From ed7df4c79e054e6e51aa4e6672e804a677dc9a94 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 09:55:36 -0600 Subject: [PATCH 001/240] feat: implement gRPC client and service for collector management --- .../config/CollectorConfiguration.java | 15 +- .../utmstack/config/GrpcConfiguration.java | 2 - .../grpc/client/CollectorServiceClient.java | 94 +++++++++ .../client/PanelCollectorServiceClient.java | 38 ++++ .../grpc/connection/GrpcConnection.java | 34 ++++ .../interceptor/CollectorAuthInterceptor.java | 45 +++++ .../GrpcInternalKeyInterceptor.java | 25 +++ .../collectors/CollectorOpsService.java | 178 ++++++------------ .../rest/collectors/UtmCollectorResource.java | 12 +- backend/src/main/proto/collector.proto | 87 +++++++++ 10 files changed, 389 insertions(+), 141 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java create mode 100644 backend/src/main/proto/collector.proto diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java index ec16e1cee..cb37b9f36 100644 --- a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java @@ -1,15 +1,12 @@ package com.park.utmstack.config; -import com.utmstack.grpc.connection.GrpcConnection; -import com.utmstack.grpc.exception.GrpcConnectionException; -import com.utmstack.grpc.jclient.config.interceptors.impl.GrpcEmptyAuthInterceptor; +import com.park.utmstack.grpc.connection.GrpcConnection; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CollectorConfiguration { - private GrpcConnection collectorConnection; @Value("${grpc.server.address}") private String serverAddress; @@ -18,9 +15,11 @@ public class CollectorConfiguration { private Integer serverPort; @Bean - public GrpcConnection collectorConnection() throws GrpcConnectionException { - this.collectorConnection = new GrpcConnection(); - this.collectorConnection.createChannel(serverAddress, serverPort, new GrpcEmptyAuthInterceptor()); - return this.collectorConnection; + public GrpcConnection collectorConnection() { + + GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); + collectorConnection.connect(); + + return collectorConnection; } } diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 8053115cf..93f6d97bd 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -2,7 +2,6 @@ import com.park.utmstack.security.GrpcInterceptor; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyChannelBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; @@ -13,7 +12,6 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; -@Configuration public class GrpcConfiguration { private ManagedChannel channel; diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java new file mode 100644 index 000000000..1760c6005 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -0,0 +1,94 @@ +package com.park.utmstack.grpc.client; + +import agent.CollectorOuterClass; +import agent.CollectorServiceGrpc; +import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; +import com.park.utmstack.service.grpc.AuthResponse; +import com.park.utmstack.service.grpc.DeleteRequest; +import com.park.utmstack.service.grpc.ListRequest; +import com.park.utmstack.util.exceptions.ApiException; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; + +@Slf4j +public class CollectorServiceClient { + + private final ManagedChannel channel; + private final CollectorServiceGrpc.CollectorServiceBlockingStub baseStub; + + public CollectorServiceClient(ManagedChannel channel) { + this.channel = channel; + this.baseStub = CollectorServiceGrpc.newBlockingStub(channel); + } + + public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { + String ctx = "CollectorServiceClient.listCollectors"; + try { + + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.listCollector(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + public AuthResponse deleteCollector(int collectorId, String collectorKey) { + + try { + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors( + new CollectorAuthInterceptor( + String.valueOf(collectorId), + collectorKey + ) + ); + + DeleteRequest request = DeleteRequest.newBuilder() + .setDeletedBy(String.valueOf(collectorId)) + .build(); + + return stub.deleteCollector(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while deleting collector:{}", collectorId, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error deleting collector", collectorId), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + + public CollectorOuterClass.CollectorConfig getCollectorConfig(int collectorId, String collectorKey, CollectorOuterClass.CollectorModule module) { + + try { + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors( + new CollectorAuthInterceptor( + String.valueOf(collectorId), + collectorKey + ) + ); + + CollectorOuterClass.ConfigRequest request = + CollectorOuterClass.ConfigRequest.newBuilder() + .setModule(module) + .build(); + + return stub.getCollectorConfig(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while getting collector:{}", collectorId, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error getting collector config", collectorId), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + public void shutdown() { + channel.shutdown(); + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java new file mode 100644 index 000000000..d1f8cd1f3 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -0,0 +1,38 @@ +package com.park.utmstack.grpc.client; + +import agent.CollectorOuterClass; +import agent.PanelCollectorServiceGrpc; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; + +public class PanelCollectorServiceClient { + + private final ManagedChannel channel; + private final PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub baseStub; + + public PanelCollectorServiceClient(ManagedChannel channel) { + this.channel = channel; + this.baseStub = PanelCollectorServiceGrpc.newBlockingStub(channel); + } + + public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { + + try { + PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.registerCollectorConfig(config); + + } catch (StatusRuntimeException e) { + throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); + } catch (Exception e) { + throw new RuntimeException("Unexpected error inserting collector config: " + e.getMessage(), e); + } + } + + public void shutdown() { + channel.shutdown(); + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java b/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java new file mode 100644 index 000000000..93c42aede --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java @@ -0,0 +1,34 @@ +package com.park.utmstack.grpc.connection; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class GrpcConnection { + + private ManagedChannel channel; + private final String host; + private final int port; + + public void connect() { + this.channel = ManagedChannelBuilder + .forAddress(host, port) + .usePlaintext() + .build(); + } + + public ManagedChannel getChannel() { + if (channel == null) { + throw new IllegalStateException("Channel not initialized. Call connect() first."); + } + return channel; + } + + public void shutdown() { + if (channel != null && !channel.isShutdown()) { + channel.shutdown(); + } + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java new file mode 100644 index 000000000..701a568fb --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java @@ -0,0 +1,45 @@ +package com.park.utmstack.grpc.interceptor; + +import io.grpc.*; + +public class CollectorAuthInterceptor implements ClientInterceptor { + + private static final Metadata.Key ID_HEADER = + Metadata.Key.of("id", Metadata.ASCII_STRING_MARSHALLER); + + private static final Metadata.Key KEY_HEADER = + Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER); + + private static final Metadata.Key TYPE_HEADER = + Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); + + private final String collectorId; + private final String collectorKey; + + public CollectorAuthInterceptor(String collectorId, String collectorKey) { + this.collectorId = collectorId; + this.collectorKey = collectorKey; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, + CallOptions callOptions, + Channel channel) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>( + channel.newCall(methodDescriptor, callOptions)) { + + @Override + public void start(Listener responseListener, Metadata headers) { + + headers.put(ID_HEADER, collectorId); + headers.put(KEY_HEADER, collectorKey); + headers.put(TYPE_HEADER, "collector"); + + super.start(responseListener, headers); + } + }; + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java new file mode 100644 index 000000000..eb620fd2a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java @@ -0,0 +1,25 @@ +package com.park.utmstack.grpc.interceptor; + +import com.park.utmstack.config.Constants; +import io.grpc.*; + +public class GrpcInternalKeyInterceptor implements ClientInterceptor { + + private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); + + @Override + public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>( + channel.newCall(methodDescriptor, callOptions)) { + + @Override public void start(Listener responseListener, Metadata headers) { + String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); + + headers.put(INTERNAL_KEY_HEADER, internalKey); + headers.put(TYPE_HEADER, "internal"); + super.start(responseListener, headers); + } }; + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index a18fae336..0d3263a5f 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -8,10 +8,6 @@ import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; import agent.CollectorOuterClass.ConfigRequest; -import agent.Common; -import agent.Common.ListRequest; -import agent.Common.AuthResponse; -import agent.Common.DeleteRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -19,6 +15,9 @@ import com.park.utmstack.domain.collector.UtmCollector; import com.park.utmstack.domain.network_scan.AssetGroupFilter; import com.park.utmstack.domain.network_scan.UtmAssetGroup; +import com.park.utmstack.grpc.client.CollectorServiceClient; +import com.park.utmstack.grpc.client.PanelCollectorServiceClient; +import com.park.utmstack.grpc.connection.GrpcConnection; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; @@ -33,21 +32,24 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.grpc.AuthResponse; +import com.park.utmstack.service.grpc.DeleteRequest; +import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; +import com.park.utmstack.util.exceptions.ApiException; import com.park.utmstack.web.rest.errors.BadRequestAlertException; -import com.utmstack.grpc.connection.GrpcConnection; import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; import com.utmstack.grpc.service.CollectorService; -import com.utmstack.grpc.service.PanelCollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -69,8 +71,8 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); private final GrpcConnection grpcConnection; - private final PanelCollectorService panelCollectorService; - private final CollectorService collectorService; + private final PanelCollectorServiceClient panelCollectorService; + private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; private final UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository; @@ -100,9 +102,10 @@ public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleService utmModuleService, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { + this.grpcConnection = grpcConnection; - this.panelCollectorService = new PanelCollectorService(grpcConnection); - this.collectorService = new CollectorService(grpcConnection); + this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); + this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; @@ -130,35 +133,15 @@ public ConfigKnowledge upsertCollectorConfig(CollectorConfig config) throws Coll } try { - return panelCollectorService.insertCollectorConfig(config, internalKey); + return panelCollectorService.insertCollectorConfig(config); } catch (Exception e) { String msg = ctx + ": " + e.getMessage(); throw new CollectorConfigurationGrpcException(msg); } } - /** - * Method to get collectors list. - * - * @param request is the request with all the pagination and search params used to list collectors - * according to those params. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. - */ - public ListCollectorsResponseDTO listCollector(ListRequest request) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".listCollector"; - - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - if (!StringUtils.hasText(internalKey)) { - throw new BadRequestAlertException(ctx + ": Internal key not configured.", ctx, CLASSNAME); - } - - try { - return mapToListCollectorsResponseDTO(collectorService.listCollector(request, internalKey)); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public ListCollectorsResponseDTO listCollector(ListRequest request) { + return mapToListCollectorsResponseDTO(collectorService.listCollectors(request)); } /** @@ -178,10 +161,10 @@ public CollectorHostnames listCollectorHostnames(ListRequest request) throws Col } try { - ListCollectorResponse response = collectorService.listCollector(request, internalKey); + ListCollectorResponse response = collectorService.listCollectors(request); CollectorHostnames collectorHostnames = new CollectorHostnames(); - response.getRowsList().forEach(c->{ + response.getRowsList().forEach(c -> { collectorHostnames.getHostname().add(c.getHostname()); }); @@ -196,48 +179,20 @@ public CollectorHostnames listCollectorHostnames(ListRequest request) throws Col * Method to get collectors by hostname and module. * * @param request contains the filter information used to search. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. */ - public ListCollectorsResponseDTO getCollectorsByHostnameAndModule(ListRequest request) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".GetCollectorsByHostnameAndModule"; - - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - if (!StringUtils.hasText(internalKey)) { - throw new BadRequestAlertException(ctx + ": Internal key not configured.", ctx, CLASSNAME); - } - - try { - return mapToListCollectorsResponseDTO(collectorService.listCollector(request, internalKey)); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public ListCollectorsResponseDTO getCollectorsByHostnameAndModule(ListRequest request) { + return mapToListCollectorsResponseDTO(collectorService.listCollectors(request)); } - /** - * Method to get a collector config from agent manager via gRPC. - * - * @param request represents the CollectorModule to get the configurations from. - * @param auth is the authentication parameters used to filter in order to get the collector configuration. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. - */ - public CollectorConfig getCollectorConfig(ConfigRequest request, AuthResponse auth) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".getCollectorConfig"; - - - try { - return collectorService.requestCollectorConfig(request, auth); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public CollectorConfig getCollectorConfig(CollectorDTO collectorDTO) { + return collectorService.getCollectorConfig(collectorDTO.getId(), collectorDTO.getCollectorKey(), + CollectorModule.valueOf(collectorDTO.getModule().toString())); } /** * Method to transform a ListCollectorResponse to ListCollectorsResponseDTO */ - private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorResponse response) throws Exception { + private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorResponse response) { final String ctx = CLASSNAME + ".mapToListCollectorsResponseDTO"; try { ListCollectorsResponseDTO dto = new ListCollectorsResponseDTO(); @@ -253,7 +208,7 @@ private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorRe return dto; } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); + throw new ApiException(String.format("%s: Error mapping ListCollectorResponse to ListCollectorsResponseDTO: %s", ctx, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -321,50 +276,40 @@ private CollectorGroupConfigurations mapToCollectorGroupConfigurations(UtmModule */ public void deleteCollector(String hostname, CollectorModuleEnum module) { final String ctx = CLASSNAME + ".deleteCollector"; - try { - String currentUser = SecurityUtils.getCurrentUserLogin().orElseThrow(() -> new RuntimeException("No current user login")); - - Optional collectorToSearch = getCollectorsByHostnameAndModule( - getListRequestByHostnameAndModule(hostname, module)).getCollectors() - .stream().findFirst(); - try { - if (collectorToSearch.isEmpty()) { - log.error(String.format("%1$s: UtmCollector %2$s could not be deleted because no information was obtained from collector manager", ctx, hostname)); - return; - } - } catch (StatusRuntimeException e) { - if (e.getStatus().getCode() == Status.Code.NOT_FOUND) { - log.error(String.format("%1$s: UtmCollector %2$s could not be deleted because was not found", ctx, hostname)); - return; - } - } - DeleteRequest collectorDelete = DeleteRequest.newBuilder().setDeletedBy(currentUser).build(); - AuthResponse auth = Common.AuthResponse.newBuilder() - .setId(collectorToSearch.get().getId()) - .setKey(collectorToSearch.get().getCollectorKey()) - .build(); - collectorService.deleteCollector(collectorDelete, auth); + var request = getListRequestByHostnameAndModule(hostname, module); + List collectors = getCollectorsByHostnameAndModule(request).getCollectors(); - } catch (Exception e) { - String msg = ctx + ": " + e.getLocalizedMessage(); - log.error(msg); - throw new RuntimeException(msg); + Optional found = collectors.stream().findFirst(); + if (found.isEmpty()) { + log.error("{}: Collector {} not found in Agent Manager", ctx, hostname); + return; } + + CollectorDTO collector = found.get(); + + collectorService.deleteCollector( + collector.getId(), + collector.getCollectorKey() + ); + + log.info("{}: Collector {} deleted successfully", ctx, hostname); + } - public List mapPasswordConfiguration( List configs) { - return configs.stream().peek(config -> { + public List mapPasswordConfiguration(List configs) { + + return configs.stream().peek(config -> { if (config.getConfDataType().equals("password")) { final UtmModuleGroupConfiguration utmModuleGroupConfiguration = utmModuleGroupConfigurationRepository.findById(config.getId()) .orElseThrow(() -> new RuntimeException(String.format("Configuration id %s not found", config.getId()))); - if (config.getConfValue().equals(utmModuleGroupConfiguration.getConfValue())){ + if (config.getConfValue().equals(utmModuleGroupConfiguration.getConfValue())) { config.setConfValue(CipherUtil.decrypt(utmModuleGroupConfiguration.getConfValue(), ENCRYPTION_KEY)); } } - }).collect(Collectors.toList()); + }).collect(Collectors.toList()); } @Transactional @@ -377,6 +322,7 @@ public void updateGroup(List collectorsIds, Long assetGroupId) throws Exce throw new Exception(ctx + ": " + e.getMessage()); } } + @Transactional public Page searchGroupsByFilter(AssetGroupFilter filter, Pageable pageable) throws Exception { final String ctx = CLASSNAME + ".searchGroupsByFilter"; @@ -492,20 +438,20 @@ public void deleteCollector(Long id) throws Exception { this.deleteCollector(collector.get().getHostname(), CollectorModuleEnum.valueOf(collector.get().getModule())); List modules = this.utmModuleGroupRepository.findAllByCollector(id.toString()); - if(!modules.isEmpty()){ + if (!modules.isEmpty()) { UtmModule module = utmModuleRepository.findById(modules.get(0).getModuleId()).get(); - if(module.getModuleActive()){ + if (module.getModuleActive()) { modules = this.utmModuleGroupRepository.findAllByModuleId(module.getId()) - .stream().filter( m -> !m.getCollector().equals(id.toString())) + .stream().filter(m -> !m.getCollector().equals(id.toString())) .toList(); - if(modules.isEmpty()){ + if (modules.isEmpty()) { this.utmModuleService.activateDeactivate(ModuleActivationDTO.builder() - .serverId(module.getServerId()) - .moduleName(module.getModuleName()) - .activationStatus(false) + .serverId(module.getServerId()) + .moduleName(module.getModuleName()) + .activationStatus(false) .build()); } } @@ -528,14 +474,7 @@ public String validateCollectorConfig(CollectorConfigKeysDTO collectorConfig) { } public CollectorConfig cacheCurrentCollectorConfig(CollectorDTO collectorDTO) throws CollectorServiceGrpcException { - return this.getCollectorConfig( - ConfigRequest.newBuilder() - .setModule(CollectorModule.valueOf(collectorDTO.getModule().toString())) - .build(), - AuthResponse.newBuilder() - .setId(collectorDTO.getId()) - .setKey(collectorDTO.getCollectorKey()) - .build()); + return this.getCollectorConfig(collectorDTO); } public void updateCollectorConfigViaGrpc( @@ -552,10 +491,10 @@ public void updateCollectorConfigurationKeys(CollectorConfigKeysDTO collectorCon try { List configs = utmModuleGroupRepository .findAllByModuleIdAndCollector(collectorConfig.getModuleId(), - String.valueOf(collectorConfig.getCollector().getId())); + String.valueOf(collectorConfig.getCollector().getId())); List keys = collectorConfig.getKeys(); - if (CollectionUtils.isEmpty(collectorConfig.getKeys())){ + if (CollectionUtils.isEmpty(collectorConfig.getKeys())) { utmModuleGroupRepository.deleteAll(configs); } else { for (UtmModuleGroupConfiguration key : keys) { @@ -590,12 +529,11 @@ public ListRequest getListRequestByHostnameAndModule(String hostname, CollectorM } else if (module != null) { query = "module.Is=" + module.name(); } - ListRequest request = ListRequest.newBuilder() + return ListRequest.newBuilder() .setPageNumber(1) .setPageSize(1000000) .setSearchQuery(query) .setSortBy("id,desc") .build(); - return request; } } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index fd962beae..441bc476c 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -1,7 +1,6 @@ package com.park.utmstack.web.rest.collectors; import agent.CollectorOuterClass.CollectorConfig; -import agent.Common.ListRequest; import com.park.utmstack.domain.application_events.enums.ApplicationEventType; import com.park.utmstack.domain.application_modules.UtmModuleGroup; import com.park.utmstack.domain.network_scan.AssetGroupFilter; @@ -19,6 +18,7 @@ import com.park.utmstack.service.dto.collectors.dto.ErrorResponse; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.util.ResponseUtil; import com.park.utmstack.web.rest.errors.BadRequestAlertException; import com.park.utmstack.web.rest.errors.InternalServerErrorException; @@ -143,11 +143,6 @@ public ResponseEntity listCollectorsByModule(@Request log.error(msg); applicationEventService.createEvent(msg, ApplicationEventType.ERROR); return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } catch (CollectorServiceGrpcException e) { - String msg = ctx + ": UtmCollector manager is not available or was an error getting the collector list. " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_GATEWAY, msg); } } @@ -208,11 +203,6 @@ public ResponseEntity listCollectorByHostNameAndModul log.error(msg); applicationEventService.createEvent(msg, ApplicationEventType.ERROR); return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } catch (CollectorServiceGrpcException e) { - String msg = ctx + ": UtmCollector manager is not available or was an error getting configuration. " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_GATEWAY, msg); } } diff --git a/backend/src/main/proto/collector.proto b/backend/src/main/proto/collector.proto new file mode 100644 index 000000000..99fd4d551 --- /dev/null +++ b/backend/src/main/proto/collector.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +option go_package = "github.com/utmstack/UTMStack/agent-manager/agent"; +import "common.proto"; + +package agent; + +service CollectorService { + rpc RegisterCollector(RegisterRequest) returns (AuthResponse) {} + rpc DeleteCollector(DeleteRequest) returns (AuthResponse) {} + rpc ListCollector (ListRequest) returns (ListCollectorResponse) {} + rpc CollectorStream(stream CollectorMessages) returns (stream CollectorMessages) {} + rpc GetCollectorConfig (ConfigRequest) returns (CollectorConfig) {} +} + +service PanelCollectorService { + rpc RegisterCollectorConfig(CollectorConfig) returns (ConfigKnowledge) {} +} + +enum CollectorModule{ + AS_400 = 0; + UTMSTACK = 1; +} + +message RegisterRequest { + string ip = 1; + string hostname = 2; + string version = 3; + CollectorModule collector = 4; +} + +message ListCollectorResponse { + repeated Collector rows = 1; + int32 total = 2; +} + +message Collector { + int32 id = 1; + Status status = 2; + string collector_key = 3; + string ip = 4; + string hostname = 5; + string version = 6; + CollectorModule module = 7; + string last_seen = 8; +} + +message CollectorMessages { + oneof stream_message { + CollectorConfig config = 1; + ConfigKnowledge result = 2; + } +} + +message CollectorConfig { + string collector_id = 1; + repeated CollectorConfigGroup groups = 2; + string request_id = 3; +} + +message CollectorConfigGroup { + int32 id = 1; + string group_name = 2; + string group_description = 3; + repeated CollectorGroupConfigurations configurations = 4; + int32 collector_id = 5; +} + +message CollectorGroupConfigurations { + int32 id = 1; + int32 group_id = 2; + string conf_key = 3; + string conf_value = 4; + string conf_name = 5; + string conf_description = 6; + string conf_data_type = 7; + bool conf_required = 8; +} + +message ConfigKnowledge{ + string accepted = 1; + string request_id = 2; +} + +message ConfigRequest { + CollectorModule module = 1; +} From 169496c889b1d039d76b046288ce6e89af2a1efd Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 10:10:53 -0600 Subject: [PATCH 002/240] feat: implement gRPC client and service for collector management --- .../main/java/com/park/utmstack/config/GrpcConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 93f6d97bd..9d014b830 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -12,6 +12,7 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; +@Configuration public class GrpcConfiguration { private ManagedChannel channel; From 29896069d60b4c97090816740d2977d3123291ae Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 11:10:51 -0600 Subject: [PATCH 003/240] feat: implement gRPC client and service for collector management --- .../service/collectors/CollectorOpsService.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 0d3263a5f..78b855cf8 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -7,7 +7,6 @@ import agent.CollectorOuterClass.Collector; import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; -import agent.CollectorOuterClass.ConfigRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -22,7 +21,6 @@ import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.repository.collector.UtmCollectorRepository; -import com.park.utmstack.security.SecurityUtils; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.application_modules.UtmModuleService; import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; @@ -32,8 +30,6 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; -import com.park.utmstack.service.grpc.AuthResponse; -import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; @@ -42,7 +38,6 @@ import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; -import com.utmstack.grpc.service.CollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; @@ -70,7 +65,7 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); - private final GrpcConnection grpcConnection; + private final ManagedChannel channel; private final PanelCollectorServiceClient panelCollectorService; private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; @@ -92,7 +87,7 @@ public class CollectorOpsService { private final CollectorValidatorService collectorValidatorService; - public CollectorOpsService(GrpcConnection grpcConnection, + public CollectorOpsService(ManagedChannel channel, UtmModuleGroupService moduleGroupService, UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository, UtmCollectorRepository utmCollectorRepository, @@ -103,9 +98,9 @@ public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { - this.grpcConnection = grpcConnection; - this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); - this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); + this.channel = channel; + this.panelCollectorService = new PanelCollectorServiceClient(channel); + this.collectorService = new CollectorServiceClient(channel); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; From b3ebe5cba7ab21b1f0c5a4144ed4662aa5e8d255 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 12:22:21 -0600 Subject: [PATCH 004/240] feat: remove unused GrpcInternalKeyInterceptor from collector service clients --- .../config/CollectorConfiguration.java | 25 ------------------- .../grpc/client/CollectorServiceClient.java | 8 +----- .../client/PanelCollectorServiceClient.java | 6 +---- .../GrpcInternalKeyInterceptor.java | 25 ------------------- 4 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java delete mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java deleted file mode 100644 index cb37b9f36..000000000 --- a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.config; - -import com.park.utmstack.grpc.connection.GrpcConnection; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class CollectorConfiguration { - - @Value("${grpc.server.address}") - private String serverAddress; - - @Value("${grpc.server.port}") - private Integer serverPort; - - @Bean - public GrpcConnection collectorConnection() { - - GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); - collectorConnection.connect(); - - return collectorConnection; - } -} diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java index 1760c6005..faa50bbc6 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -3,7 +3,6 @@ import agent.CollectorOuterClass; import agent.CollectorServiceGrpc; import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import com.park.utmstack.service.grpc.AuthResponse; import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; @@ -27,12 +26,7 @@ public CollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { String ctx = "CollectorServiceClient.listCollectors"; try { - - CollectorServiceGrpc.CollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.listCollector(request); - + return baseStub.listCollector(request); } catch (StatusRuntimeException e) { log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java index d1f8cd1f3..db0e30e76 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -2,7 +2,6 @@ import agent.CollectorOuterClass; import agent.PanelCollectorServiceGrpc; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; @@ -19,10 +18,7 @@ public PanelCollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { try { - PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.registerCollectorConfig(config); + return baseStub.registerCollectorConfig(config); } catch (StatusRuntimeException e) { throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java deleted file mode 100644 index eb620fd2a..000000000 --- a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.grpc.interceptor; - -import com.park.utmstack.config.Constants; -import io.grpc.*; - -public class GrpcInternalKeyInterceptor implements ClientInterceptor { - - private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); - private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); - - @Override - public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { - - return new ForwardingClientCall.SimpleForwardingClientCall<>( - channel.newCall(methodDescriptor, callOptions)) { - - @Override public void start(Listener responseListener, Metadata headers) { - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - headers.put(INTERNAL_KEY_HEADER, internalKey); - headers.put(TYPE_HEADER, "internal"); - super.start(responseListener, headers); - } }; - } -} From 936c870a18890b4ddb224252790d694e987d3f16 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 15:22:03 -0600 Subject: [PATCH 005/240] fix(module.service): return full response body instead of filtering AS_400 module Signed-off-by: Manuel Abascal --- frontend/src/app/app-module/services/module.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/app-module/services/module.service.ts b/frontend/src/app/app-module/services/module.service.ts index 6b960b13e..bceda8763 100644 --- a/frontend/src/app/app-module/services/module.service.ts +++ b/frontend/src/app/app-module/services/module.service.ts @@ -84,7 +84,7 @@ export class ModuleService { m.prettyName = m.prettyName + ' GravityZone'; } }); - return response.body.filter(m => m.moduleName !== this.utmModulesEnum.AS_400); + return response.body; }), catchError(error => { console.error(error); From df509bfb16b31643fd9c9faed0c8ab772edef40f Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 13:41:11 -0600 Subject: [PATCH 006/240] feat: add CollectorConfigDTO and unique server name validation --- .../validators/UniqueServerName.java | 18 +++++++++ .../validators/UniqueServerNameValidator.java | 27 +++++++++++++ .../collectors/dto/CollectorConfigDTO.java | 27 +++++++++++++ .../dto/CollectorConfigKeysDTO.java | 39 ------------------- 4 files changed, 72 insertions(+), 39 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java create mode 100644 backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java create mode 100644 backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java delete mode 100644 backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java diff --git a/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java new file mode 100644 index 000000000..de04fc86a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java @@ -0,0 +1,18 @@ +package com.park.utmstack.domain.collector.validators; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = UniqueServerNameValidator.class) +public @interface UniqueServerName { + String message() default "Server name must be unique."; + Class[] groups() default {}; + Class[] payload() default {}; +} + diff --git a/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java new file mode 100644 index 000000000..437fcbcd1 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java @@ -0,0 +1,27 @@ +package com.park.utmstack.domain.collector.validators; + +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; +import java.util.stream.Collectors; + +public class UniqueServerNameValidator implements ConstraintValidator> { + + @Override + public boolean isValid(List keys, ConstraintValidatorContext context) { + + if (keys == null || keys.isEmpty()) return false; + + long duplicates = keys.stream() + .filter(k -> "Hostname".equals(k.getConfName())) + .collect(Collectors.groupingBy(UtmModuleGroupConfiguration::getConfValue, Collectors.counting())) + .values().stream() + .filter(count -> count > 1) + .count(); + + return duplicates == 0; + + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java new file mode 100644 index 000000000..562d1bad6 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java @@ -0,0 +1,27 @@ +package com.park.utmstack.service.dto.collectors.dto; + +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; +import com.park.utmstack.domain.collector.validators.UniqueServerName; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Setter +@Getter +public class CollectorConfigDTO { + + @NotNull + CollectorDTO collector; + + @NotNull + private Long moduleId; + + @NotNull + @NotEmpty + @UniqueServerName + private List keys; + +} diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java deleted file mode 100644 index 40af52707..000000000 --- a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.park.utmstack.service.dto.collectors.dto; - -import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; - -import javax.validation.constraints.NotNull; -import java.util.List; - -public class CollectorConfigKeysDTO { - @NotNull - CollectorDTO collector; - @NotNull - private Long moduleId; - @NotNull - private List keys; - - public Long getModuleId() { - return moduleId; - } - - public void setModuleId(Long moduleId) { - this.moduleId = moduleId; - } - - public List getKeys() { - return keys; - } - - public void setKeys(List keys) { - this.keys = keys; - } - - public CollectorDTO getCollector() { - return collector; - } - - public void setCollector(CollectorDTO collector) { - this.collector = collector; - } -} From f833670075e288e4b53423179b8a80d7fbc28a0c Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:17:10 -0600 Subject: [PATCH 007/240] feat: add CollectorConfigBuilder for constructing CollectorConfig from DTO --- .../collectors/CollectorConfigBuilder.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java new file mode 100644 index 000000000..cd821aa30 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java @@ -0,0 +1,108 @@ +package com.park.utmstack.service.collectors; + +import agent.CollectorOuterClass.CollectorConfig; +import agent.CollectorOuterClass.CollectorConfigGroup; +import agent.CollectorOuterClass.CollectorGroupConfigurations; +import com.park.utmstack.config.Constants; +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; +import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; +import com.park.utmstack.service.application_modules.UtmModuleGroupService; +import com.park.utmstack.util.CipherUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Component +@RequiredArgsConstructor +public class CollectorConfigBuilder { + + private final UtmModuleGroupService moduleGroupService; + private final UtmModuleGroupConfigurationRepository configRepo; + + public CollectorConfig build(CollectorConfigDTO dto) { + + List processed = processPasswords(dto.getKeys()); + + return buildCollectorConfig(processed, dto.getCollector()); + } + + + private List processPasswords(List configs) { + + return configs.stream().map(config -> { + + if (Constants.CONF_TYPE_PASSWORD.equals(config.getConfDataType())) { + + UtmModuleGroupConfiguration original = configRepo.findById(config.getId()) + .orElseThrow(() -> new RuntimeException("Configuration id " + config.getId() + " not found")); + + if (Objects.equals(config.getConfValue(), original.getConfValue())) { + config.setConfValue( + CipherUtil.decrypt(original.getConfValue(), System.getenv(Constants.ENV_ENCRYPTION_KEY)) + ); + } + } + + return config; + + }).toList(); + } + + + private CollectorConfig buildCollectorConfig(List keys, CollectorDTO collectorDTO) { + + List groupIds = keys.stream() + .map(UtmModuleGroupConfiguration::getGroupId) + .distinct() + .toList(); + + List groups = new ArrayList<>(); + + for (Long groupId : groupIds) { + + moduleGroupService.findOne(groupId).ifPresent(group -> { + + List configs = + keys.stream() + .filter(k -> k.getGroupId().equals(groupId)) + .map(this::mapToCollectorGroupConfigurations) + .toList(); + + groups.add( + CollectorConfigGroup.newBuilder() + .setGroupName(group.getGroupName()) + .setGroupDescription(group.getGroupDescription()) + .addAllConfigurations(configs) + .setCollectorId(collectorDTO.getId()) + .build() + ); + }); + } + + return CollectorConfig.newBuilder() + .setCollectorId(String.valueOf(collectorDTO.getId())) + .setRequestId(String.valueOf(System.currentTimeMillis())) + .addAllGroups(groups) + .build(); + } + + + private CollectorGroupConfigurations mapToCollectorGroupConfigurations( + UtmModuleGroupConfiguration c) { + + return CollectorGroupConfigurations.newBuilder() + .setConfKey(c.getConfKey()) + .setConfName(c.getConfName()) + .setConfDescription(c.getConfDescription()) + .setConfDataType(c.getConfDataType()) + .setConfValue(c.getConfValue()) + .setConfRequired(c.getConfRequired()) + .build(); + } +} + From 9905700175039fb3d82cafca84381978c1f25b64 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:17:34 -0600 Subject: [PATCH 008/240] feat: add CollectorGrpcService for managing collector operations via gRPC --- .../collectors/CollectorGrpcService.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java new file mode 100644 index 000000000..073900217 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java @@ -0,0 +1,36 @@ +package com.park.utmstack.service.collectors; + +import agent.CollectorOuterClass.*; +import com.park.utmstack.grpc.client.CollectorServiceClient; +import com.park.utmstack.grpc.client.PanelCollectorServiceClient; +import com.park.utmstack.service.grpc.ListRequest; +import io.grpc.ManagedChannel; +import org.springframework.stereotype.Service; + +@Service +public class CollectorGrpcService { + + private final CollectorServiceClient collectorClient; + private final PanelCollectorServiceClient panelClient; + + public CollectorGrpcService(ManagedChannel channel) { + this.collectorClient = new CollectorServiceClient(channel); + this.panelClient = new PanelCollectorServiceClient(channel); + } + + public ListCollectorResponse listCollectors(ListRequest request) { + return collectorClient.listCollectors(request); + } + + public CollectorConfig getCollectorConfig(int id, String key, CollectorModule module) { + return collectorClient.getCollectorConfig(id, key, module); + } + + public void deleteCollector(int id, String key) { + collectorClient.deleteCollector(id, key); + } + + public ConfigKnowledge upsertCollectorConfig(CollectorConfig config) { + return panelClient.insertCollectorConfig(config); + } +} From 505927a444166485bab90311cae08d633a2580a7 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:05 -0600 Subject: [PATCH 009/240] feat: update CollectorConfig validation and add CollectorService for gRPC integration --- .../collectors/CollectorOpsService.java | 9 +++--- .../service/collectors/CollectorService.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 78b855cf8..df0ef0452 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -16,7 +16,6 @@ import com.park.utmstack.domain.network_scan.UtmAssetGroup; import com.park.utmstack.grpc.client.CollectorServiceClient; import com.park.utmstack.grpc.client.PanelCollectorServiceClient; -import com.park.utmstack.grpc.connection.GrpcConnection; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; @@ -26,7 +25,7 @@ import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; import com.park.utmstack.service.dto.collectors.CollectorHostnames; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; @@ -458,7 +457,7 @@ public void deleteCollector(Long id) throws Exception { } - public String validateCollectorConfig(CollectorConfigKeysDTO collectorConfig) { + public String validateCollectorConfig(CollectorConfigDTO collectorConfig) { Errors errors = new BeanPropertyBindingResult(collectorConfig, "updateConfigurationKeysBody"); collectorValidatorService.validate(collectorConfig, errors); @@ -473,7 +472,7 @@ public CollectorConfig cacheCurrentCollectorConfig(CollectorDTO collectorDTO) th } public void updateCollectorConfigViaGrpc( - CollectorConfigKeysDTO collectorConfig, + CollectorConfigDTO collectorConfig, CollectorDTO collectorDTO) throws CollectorConfigurationGrpcException { this.upsertCollectorConfig( @@ -481,7 +480,7 @@ public void updateCollectorConfigViaGrpc( this.mapPasswordConfiguration(collectorConfig.getKeys()), collectorDTO)); } - public void updateCollectorConfigurationKeys(CollectorConfigKeysDTO collectorConfig) throws Exception { + public void updateCollectorConfigurationKeys(CollectorConfigDTO collectorConfig) throws Exception { final String ctx = CLASSNAME + ".updateCollectorConfigurationKeys"; try { List configs = utmModuleGroupRepository diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java new file mode 100644 index 000000000..bbffd535a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java @@ -0,0 +1,28 @@ +package com.park.utmstack.service.collectors; + +import agent.CollectorOuterClass; +import com.park.utmstack.service.application_events.ApplicationEventService; +import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CollectorService { + + private final CollectorGrpcService collectorGrpcService; + private final ApplicationEventService eventService; + private final UtmModuleGroupConfigurationService moduleGroupConfigurationService; + private final CollectorConfigBuilder CollectorConfigBuilder; + + public void upsertCollectorConfig(CollectorConfigDTO collectorConfig) { + + this.moduleGroupConfigurationService.updateConfigurationKeys(collectorConfig.getModuleId(), collectorConfig.getKeys()); + + CollectorOuterClass.CollectorConfig collector = CollectorConfigBuilder.build(collectorConfig); + collectorGrpcService.upsertCollectorConfig(collector); + } + +} + From d45729759533197bdeddd70094078b772de79e10 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:29 -0600 Subject: [PATCH 010/240] feat: update CollectorValidatorService to use CollectorConfigDTO for validation --- .../validators/collector/CollectorValidatorService.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java b/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java index ceaccbfaf..02d1bca1b 100644 --- a/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java +++ b/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java @@ -1,8 +1,7 @@ package com.park.utmstack.service.validators.collector; import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; -import com.park.utmstack.web.rest.application_modules.UtmModuleGroupConfigurationResource; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import org.springframework.stereotype.Service; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -14,12 +13,12 @@ public class CollectorValidatorService implements Validator { @Override public boolean supports(Class clazz) { - return CollectorConfigKeysDTO.class.equals(clazz); + return CollectorConfigDTO.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { - CollectorConfigKeysDTO updateConfigurationKeysBody = (CollectorConfigKeysDTO) target; + CollectorConfigDTO updateConfigurationKeysBody = (CollectorConfigDTO) target; Map hostNames = updateConfigurationKeysBody.getKeys().stream() .filter(config -> config.getConfName().equals("Hostname")) From 80c8254300081ff5656fff4e904df023286540ce Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:34 -0600 Subject: [PATCH 011/240] feat: refactor UtmCollectorResource to use CollectorConfigDTO and CollectorOpsService --- .../rest/collectors/UtmCollectorResource.java | 77 ++++++------------- 1 file changed, 24 insertions(+), 53 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index 441bc476c..f6dfc8303 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -9,10 +9,11 @@ import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.collectors.CollectorOpsService; +import com.park.utmstack.service.collectors.CollectorService; import com.park.utmstack.service.collectors.UtmCollectorService; import com.park.utmstack.service.dto.collectors.CollectorActionEnum; import com.park.utmstack.service.dto.collectors.CollectorHostnames; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; import com.park.utmstack.service.dto.collectors.dto.ErrorResponse; @@ -27,6 +28,7 @@ import com.park.utmstack.web.rest.util.PaginationUtil; import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; +import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springdoc.api.annotations.ParameterObject; @@ -48,35 +50,20 @@ * REST controller for managing {@link UtmCollectorResource}. */ @RestController +@RequiredArgsConstructor @RequestMapping("/api") public class UtmCollectorResource { private static final String CLASSNAME = "UtmCollectorResource"; - private final CollectorOpsService collectorService; + private final CollectorOpsService collectorOpsService; private final Logger log = LoggerFactory.getLogger(UtmCollectorResource.class); private final ApplicationEventService applicationEventService; private final UtmModuleGroupConfigurationService moduleGroupConfigurationService; - private final UtmModuleGroupService moduleGroupService; - private final ApplicationEventService eventService; - private final UtmCollectorService utmCollectorService; + private final CollectorService collectorService; - public UtmCollectorResource(CollectorOpsService collectorService, - ApplicationEventService applicationEventService, - UtmModuleGroupConfigurationService moduleGroupConfigurationService, - UtmModuleGroupService moduleGroupService, - ApplicationEventService eventService, - UtmCollectorService utmCollectorService) { - - this.collectorService = collectorService; - this.applicationEventService = applicationEventService; - this.moduleGroupConfigurationService = moduleGroupConfigurationService; - this.moduleGroupService = moduleGroupService; - this.eventService = eventService; - this.utmCollectorService = utmCollectorService; - } /** * {@code POST /collector-config} : Create or update the collector configs. @@ -87,27 +74,11 @@ public UtmCollectorResource(CollectorOpsService collectorService, * persist the configurations. */ @PostMapping("/collector-config") - public ResponseEntity upsertCollectorConfig( - @Valid @RequestBody CollectorConfigKeysDTO collectorConfig, - @RequestParam(name = "action", defaultValue = "CREATE") CollectorActionEnum action) { - - final String ctx = CLASSNAME + ".upsertCollectorConfig"; - CollectorConfig cacheConfig = null; + public ResponseEntity upsertCollectorConfig(@Valid @RequestBody CollectorConfigDTO collectorConfig, + @RequestParam(name = "action", defaultValue = "CREATE") CollectorActionEnum action) { - // Validate collector configuration - String validationErrorMessage = this.collectorService.validateCollectorConfig(collectorConfig); - if (validationErrorMessage != null) { - return logAndResponse(new ErrorResponse(validationErrorMessage, HttpStatus.PRECONDITION_FAILED)); - } - - try { - cacheConfig = this.collectorService.cacheCurrentCollectorConfig(collectorConfig.getCollector()); - this.upsert(collectorConfig); - return ResponseEntity.noContent().build(); - - } catch (Exception e) { - return handleUpdateError(e, cacheConfig, collectorConfig.getCollector()); - } + collectorService.upsertCollectorConfig(collectorConfig); + return ResponseEntity.noContent().build(); } /** @@ -134,7 +105,7 @@ public ResponseEntity listCollectorsByModule(@Request .setSortBy(sortBy != null ? sortBy : "") .build(); - ListCollectorsResponseDTO response = collectorService.listCollector(request); + ListCollectorsResponseDTO response = collectorOpsService.listCollector(request); HttpHeaders headers = new HttpHeaders(); headers.add("X-Total-Count", Long.toString(response.getTotal())); return ResponseEntity.ok().headers(headers).body(response); @@ -169,7 +140,7 @@ public ResponseEntity listCollectorHostNames(@RequestParam(r .setSearchQuery(module != null ? "module.Is=" + module : "") .setSortBy(sortBy != null ? sortBy : "") .build(); - return ResponseEntity.ok().body(collectorService.listCollectorHostnames(request)); + return ResponseEntity.ok().body(collectorOpsService.listCollectorHostnames(request)); } catch (BadRequestAlertException e) { String msg = ctx + ": " + e.getLocalizedMessage(); log.error(msg); @@ -196,8 +167,8 @@ public ResponseEntity listCollectorByHostNameAndModul @RequestParam CollectorModuleEnum module) { final String ctx = CLASSNAME + ".listCollectorByHostNameAndModule"; try { - return ResponseEntity.ok().body(collectorService.listCollector( - collectorService.getListRequestByHostnameAndModule(hostname, module))); + return ResponseEntity.ok().body(collectorOpsService.listCollector( + collectorOpsService.getListRequestByHostnameAndModule(hostname, module))); } catch (BadRequestAlertException e) { String msg = ctx + ": " + e.getLocalizedMessage(); log.error(msg); @@ -224,7 +195,7 @@ public ResponseEntity> getModuleGroups(@PathVariable String public ResponseEntity updateGroup(@Valid @RequestBody UtmNetworkScanResource.UpdateGroupRequestBody body) { final String ctx = CLASSNAME + ".updateGroup"; try { - collectorService.updateGroup(body.getAssetsIds(), body.getAssetGroupId()); + collectorOpsService.updateGroup(body.getAssetsIds(), body.getAssetGroupId()); return ResponseEntity.ok().build(); } catch (Exception e) { String msg = ctx + ": " + e.getMessage(); @@ -240,7 +211,7 @@ public ResponseEntity> searchGroupsByFilter(AssetGroupFilter final String ctx = CLASSNAME + ".searchGroupsByFilter"; try { - Page page = collectorService.searchGroupsByFilter(filter, pageable); + Page page = collectorOpsService.searchGroupsByFilter(filter, pageable); HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/utm-asset-groups/searchGroupsByFilter"); return ResponseEntity.ok().headers(headers).body(page.getContent()); } catch (Exception e) { @@ -257,7 +228,7 @@ public ResponseEntity> searchByFilters(@ParameterObject Netwo @ParameterObject Pageable pageable) { final String ctx = CLASSNAME + ".searchByFilters"; try { - collectorService.listCollector(ListRequest.newBuilder() + collectorOpsService.listCollector(ListRequest.newBuilder() .setPageNumber(0) .setPageSize(1000000) .setSortBy("") @@ -279,7 +250,7 @@ public ResponseEntity deleteCollector(@PathVariable Long id) { try { log.debug("REST request to delete UtmCollector : {}", id); - collectorService.deleteCollector(id); + collectorOpsService.deleteCollector(id); return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert("UtmCollector", id.toString())).build(); } catch (Exception e) { applicationEventService.createEvent(e.getMessage(), ApplicationEventType.ERROR); @@ -289,17 +260,17 @@ public ResponseEntity deleteCollector(@PathVariable Long id) { } @PostMapping("/collectors-config") - public ResponseEntity> upsertCollectorsConfig(@RequestBody List collectors) { + public ResponseEntity> upsertCollectorsConfig(@RequestBody List collectors) { Map results = new HashMap<>(); final String ctx = CLASSNAME + ".upsertCollectorsConfig"; CollectorConfig cacheConfig = null; List> collectorsResults = new ArrayList<>(); - for (CollectorConfigKeysDTO collectorConfig : collectors) { + for (CollectorConfigDTO collectorConfig : collectors) { Map collectorResult = new HashMap<>(); collectorResult.put("collectorId", collectorConfig.getCollector().getId()); try { - cacheConfig = this.collectorService.cacheCurrentCollectorConfig(collectorConfig.getCollector()); + cacheConfig = this.collectorOpsService.cacheCurrentCollectorConfig(collectorConfig.getCollector()); this.upsert(collectorConfig); collectorResult.put("status", "success"); } catch (Exception e) { @@ -355,12 +326,12 @@ private ResponseEntity logAndResponse(ErrorResponse error) { return ResponseUtil.buildErrorResponse(error.getStatus(), error.getMessage()); } - private void upsert(CollectorConfigKeysDTO collectorConfig) throws Exception { + private void upsert(CollectorConfigDTO collectorConfig) throws Exception { // Update local database with new configuration - this.collectorService.updateCollectorConfigurationKeys(collectorConfig); + this.collectorOpsService.updateCollectorConfigurationKeys(collectorConfig); // Attempt to update collector configuration via gRPC - this.collectorService.updateCollectorConfigViaGrpc(collectorConfig, collectorConfig.getCollector()); + this.collectorOpsService.updateCollectorConfigViaGrpc(collectorConfig, collectorConfig.getCollector()); } } From ac8b907fd4b11a5e56f2affa1308ec79b1ab4433 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:49 -0600 Subject: [PATCH 012/240] feat: add logging to updateConfigurationKeys method in UtmModuleGroupConfigurationService --- .../UtmModuleGroupConfigurationService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java index 1311b5f4d..be415d1fb 100644 --- a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java +++ b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java @@ -10,6 +10,7 @@ import com.park.utmstack.util.CipherUtil; import com.park.utmstack.util.exceptions.ApiException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,6 +29,7 @@ @Service @Transactional @RequiredArgsConstructor +@Slf4j public class UtmModuleGroupConfigurationService { private static final String CLASSNAME = "UtmModuleGroupConfigurationService"; @@ -54,7 +56,7 @@ public void createConfigurationKeys(List keys) thro * @param keys List of configuration keys to save * @throws Exception In case of any error */ - public UtmModule updateConfigurationKeys(Long moduleId, List keys) throws Exception { + public UtmModule updateConfigurationKeys(Long moduleId, List keys) { final String ctx = CLASSNAME + ".updateConfigurationKeys"; try { if (CollectionUtils.isEmpty(keys)) @@ -77,7 +79,8 @@ public UtmModule updateConfigurationKeys(Long moduleId, List new ApiException(String.format("Module with ID %1$s not found", moduleId), HttpStatus.NOT_FOUND)); } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); + log.error("{}: Error updating configuration keys: {}", ctx, e.getMessage()); + throw new ApiException(String.format("%s: Error updating configuration keys: %s", ctx, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } } From d4cfb1b2a23e4172e059558cd04bd8e60d90635f Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 09:55:36 -0600 Subject: [PATCH 013/240] feat: implement gRPC client and service for collector management --- .../config/CollectorConfiguration.java | 15 +- .../utmstack/config/GrpcConfiguration.java | 2 - .../grpc/client/CollectorServiceClient.java | 94 +++++++++ .../client/PanelCollectorServiceClient.java | 38 ++++ .../grpc/connection/GrpcConnection.java | 34 ++++ .../interceptor/CollectorAuthInterceptor.java | 45 +++++ .../GrpcInternalKeyInterceptor.java | 25 +++ .../collectors/CollectorOpsService.java | 178 ++++++------------ .../rest/collectors/UtmCollectorResource.java | 12 +- backend/src/main/proto/collector.proto | 87 +++++++++ 10 files changed, 389 insertions(+), 141 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java create mode 100644 backend/src/main/proto/collector.proto diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java index ec16e1cee..cb37b9f36 100644 --- a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java @@ -1,15 +1,12 @@ package com.park.utmstack.config; -import com.utmstack.grpc.connection.GrpcConnection; -import com.utmstack.grpc.exception.GrpcConnectionException; -import com.utmstack.grpc.jclient.config.interceptors.impl.GrpcEmptyAuthInterceptor; +import com.park.utmstack.grpc.connection.GrpcConnection; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CollectorConfiguration { - private GrpcConnection collectorConnection; @Value("${grpc.server.address}") private String serverAddress; @@ -18,9 +15,11 @@ public class CollectorConfiguration { private Integer serverPort; @Bean - public GrpcConnection collectorConnection() throws GrpcConnectionException { - this.collectorConnection = new GrpcConnection(); - this.collectorConnection.createChannel(serverAddress, serverPort, new GrpcEmptyAuthInterceptor()); - return this.collectorConnection; + public GrpcConnection collectorConnection() { + + GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); + collectorConnection.connect(); + + return collectorConnection; } } diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 8053115cf..93f6d97bd 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -2,7 +2,6 @@ import com.park.utmstack.security.GrpcInterceptor; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyChannelBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; @@ -13,7 +12,6 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; -@Configuration public class GrpcConfiguration { private ManagedChannel channel; diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java new file mode 100644 index 000000000..1760c6005 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -0,0 +1,94 @@ +package com.park.utmstack.grpc.client; + +import agent.CollectorOuterClass; +import agent.CollectorServiceGrpc; +import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; +import com.park.utmstack.service.grpc.AuthResponse; +import com.park.utmstack.service.grpc.DeleteRequest; +import com.park.utmstack.service.grpc.ListRequest; +import com.park.utmstack.util.exceptions.ApiException; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; + +@Slf4j +public class CollectorServiceClient { + + private final ManagedChannel channel; + private final CollectorServiceGrpc.CollectorServiceBlockingStub baseStub; + + public CollectorServiceClient(ManagedChannel channel) { + this.channel = channel; + this.baseStub = CollectorServiceGrpc.newBlockingStub(channel); + } + + public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { + String ctx = "CollectorServiceClient.listCollectors"; + try { + + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.listCollector(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + public AuthResponse deleteCollector(int collectorId, String collectorKey) { + + try { + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors( + new CollectorAuthInterceptor( + String.valueOf(collectorId), + collectorKey + ) + ); + + DeleteRequest request = DeleteRequest.newBuilder() + .setDeletedBy(String.valueOf(collectorId)) + .build(); + + return stub.deleteCollector(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while deleting collector:{}", collectorId, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error deleting collector", collectorId), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + + public CollectorOuterClass.CollectorConfig getCollectorConfig(int collectorId, String collectorKey, CollectorOuterClass.CollectorModule module) { + + try { + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors( + new CollectorAuthInterceptor( + String.valueOf(collectorId), + collectorKey + ) + ); + + CollectorOuterClass.ConfigRequest request = + CollectorOuterClass.ConfigRequest.newBuilder() + .setModule(module) + .build(); + + return stub.getCollectorConfig(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while getting collector:{}", collectorId, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error getting collector config", collectorId), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + public void shutdown() { + channel.shutdown(); + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java new file mode 100644 index 000000000..d1f8cd1f3 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -0,0 +1,38 @@ +package com.park.utmstack.grpc.client; + +import agent.CollectorOuterClass; +import agent.PanelCollectorServiceGrpc; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; + +public class PanelCollectorServiceClient { + + private final ManagedChannel channel; + private final PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub baseStub; + + public PanelCollectorServiceClient(ManagedChannel channel) { + this.channel = channel; + this.baseStub = PanelCollectorServiceGrpc.newBlockingStub(channel); + } + + public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { + + try { + PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.registerCollectorConfig(config); + + } catch (StatusRuntimeException e) { + throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); + } catch (Exception e) { + throw new RuntimeException("Unexpected error inserting collector config: " + e.getMessage(), e); + } + } + + public void shutdown() { + channel.shutdown(); + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java b/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java new file mode 100644 index 000000000..93c42aede --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java @@ -0,0 +1,34 @@ +package com.park.utmstack.grpc.connection; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class GrpcConnection { + + private ManagedChannel channel; + private final String host; + private final int port; + + public void connect() { + this.channel = ManagedChannelBuilder + .forAddress(host, port) + .usePlaintext() + .build(); + } + + public ManagedChannel getChannel() { + if (channel == null) { + throw new IllegalStateException("Channel not initialized. Call connect() first."); + } + return channel; + } + + public void shutdown() { + if (channel != null && !channel.isShutdown()) { + channel.shutdown(); + } + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java new file mode 100644 index 000000000..701a568fb --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java @@ -0,0 +1,45 @@ +package com.park.utmstack.grpc.interceptor; + +import io.grpc.*; + +public class CollectorAuthInterceptor implements ClientInterceptor { + + private static final Metadata.Key ID_HEADER = + Metadata.Key.of("id", Metadata.ASCII_STRING_MARSHALLER); + + private static final Metadata.Key KEY_HEADER = + Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER); + + private static final Metadata.Key TYPE_HEADER = + Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); + + private final String collectorId; + private final String collectorKey; + + public CollectorAuthInterceptor(String collectorId, String collectorKey) { + this.collectorId = collectorId; + this.collectorKey = collectorKey; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, + CallOptions callOptions, + Channel channel) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>( + channel.newCall(methodDescriptor, callOptions)) { + + @Override + public void start(Listener responseListener, Metadata headers) { + + headers.put(ID_HEADER, collectorId); + headers.put(KEY_HEADER, collectorKey); + headers.put(TYPE_HEADER, "collector"); + + super.start(responseListener, headers); + } + }; + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java new file mode 100644 index 000000000..eb620fd2a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java @@ -0,0 +1,25 @@ +package com.park.utmstack.grpc.interceptor; + +import com.park.utmstack.config.Constants; +import io.grpc.*; + +public class GrpcInternalKeyInterceptor implements ClientInterceptor { + + private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); + + @Override + public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>( + channel.newCall(methodDescriptor, callOptions)) { + + @Override public void start(Listener responseListener, Metadata headers) { + String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); + + headers.put(INTERNAL_KEY_HEADER, internalKey); + headers.put(TYPE_HEADER, "internal"); + super.start(responseListener, headers); + } }; + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index a18fae336..0d3263a5f 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -8,10 +8,6 @@ import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; import agent.CollectorOuterClass.ConfigRequest; -import agent.Common; -import agent.Common.ListRequest; -import agent.Common.AuthResponse; -import agent.Common.DeleteRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -19,6 +15,9 @@ import com.park.utmstack.domain.collector.UtmCollector; import com.park.utmstack.domain.network_scan.AssetGroupFilter; import com.park.utmstack.domain.network_scan.UtmAssetGroup; +import com.park.utmstack.grpc.client.CollectorServiceClient; +import com.park.utmstack.grpc.client.PanelCollectorServiceClient; +import com.park.utmstack.grpc.connection.GrpcConnection; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; @@ -33,21 +32,24 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.grpc.AuthResponse; +import com.park.utmstack.service.grpc.DeleteRequest; +import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; +import com.park.utmstack.util.exceptions.ApiException; import com.park.utmstack.web.rest.errors.BadRequestAlertException; -import com.utmstack.grpc.connection.GrpcConnection; import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; import com.utmstack.grpc.service.CollectorService; -import com.utmstack.grpc.service.PanelCollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -69,8 +71,8 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); private final GrpcConnection grpcConnection; - private final PanelCollectorService panelCollectorService; - private final CollectorService collectorService; + private final PanelCollectorServiceClient panelCollectorService; + private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; private final UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository; @@ -100,9 +102,10 @@ public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleService utmModuleService, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { + this.grpcConnection = grpcConnection; - this.panelCollectorService = new PanelCollectorService(grpcConnection); - this.collectorService = new CollectorService(grpcConnection); + this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); + this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; @@ -130,35 +133,15 @@ public ConfigKnowledge upsertCollectorConfig(CollectorConfig config) throws Coll } try { - return panelCollectorService.insertCollectorConfig(config, internalKey); + return panelCollectorService.insertCollectorConfig(config); } catch (Exception e) { String msg = ctx + ": " + e.getMessage(); throw new CollectorConfigurationGrpcException(msg); } } - /** - * Method to get collectors list. - * - * @param request is the request with all the pagination and search params used to list collectors - * according to those params. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. - */ - public ListCollectorsResponseDTO listCollector(ListRequest request) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".listCollector"; - - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - if (!StringUtils.hasText(internalKey)) { - throw new BadRequestAlertException(ctx + ": Internal key not configured.", ctx, CLASSNAME); - } - - try { - return mapToListCollectorsResponseDTO(collectorService.listCollector(request, internalKey)); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public ListCollectorsResponseDTO listCollector(ListRequest request) { + return mapToListCollectorsResponseDTO(collectorService.listCollectors(request)); } /** @@ -178,10 +161,10 @@ public CollectorHostnames listCollectorHostnames(ListRequest request) throws Col } try { - ListCollectorResponse response = collectorService.listCollector(request, internalKey); + ListCollectorResponse response = collectorService.listCollectors(request); CollectorHostnames collectorHostnames = new CollectorHostnames(); - response.getRowsList().forEach(c->{ + response.getRowsList().forEach(c -> { collectorHostnames.getHostname().add(c.getHostname()); }); @@ -196,48 +179,20 @@ public CollectorHostnames listCollectorHostnames(ListRequest request) throws Col * Method to get collectors by hostname and module. * * @param request contains the filter information used to search. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. */ - public ListCollectorsResponseDTO getCollectorsByHostnameAndModule(ListRequest request) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".GetCollectorsByHostnameAndModule"; - - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - if (!StringUtils.hasText(internalKey)) { - throw new BadRequestAlertException(ctx + ": Internal key not configured.", ctx, CLASSNAME); - } - - try { - return mapToListCollectorsResponseDTO(collectorService.listCollector(request, internalKey)); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public ListCollectorsResponseDTO getCollectorsByHostnameAndModule(ListRequest request) { + return mapToListCollectorsResponseDTO(collectorService.listCollectors(request)); } - /** - * Method to get a collector config from agent manager via gRPC. - * - * @param request represents the CollectorModule to get the configurations from. - * @param auth is the authentication parameters used to filter in order to get the collector configuration. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. - */ - public CollectorConfig getCollectorConfig(ConfigRequest request, AuthResponse auth) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".getCollectorConfig"; - - - try { - return collectorService.requestCollectorConfig(request, auth); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public CollectorConfig getCollectorConfig(CollectorDTO collectorDTO) { + return collectorService.getCollectorConfig(collectorDTO.getId(), collectorDTO.getCollectorKey(), + CollectorModule.valueOf(collectorDTO.getModule().toString())); } /** * Method to transform a ListCollectorResponse to ListCollectorsResponseDTO */ - private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorResponse response) throws Exception { + private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorResponse response) { final String ctx = CLASSNAME + ".mapToListCollectorsResponseDTO"; try { ListCollectorsResponseDTO dto = new ListCollectorsResponseDTO(); @@ -253,7 +208,7 @@ private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorRe return dto; } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); + throw new ApiException(String.format("%s: Error mapping ListCollectorResponse to ListCollectorsResponseDTO: %s", ctx, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -321,50 +276,40 @@ private CollectorGroupConfigurations mapToCollectorGroupConfigurations(UtmModule */ public void deleteCollector(String hostname, CollectorModuleEnum module) { final String ctx = CLASSNAME + ".deleteCollector"; - try { - String currentUser = SecurityUtils.getCurrentUserLogin().orElseThrow(() -> new RuntimeException("No current user login")); - - Optional collectorToSearch = getCollectorsByHostnameAndModule( - getListRequestByHostnameAndModule(hostname, module)).getCollectors() - .stream().findFirst(); - try { - if (collectorToSearch.isEmpty()) { - log.error(String.format("%1$s: UtmCollector %2$s could not be deleted because no information was obtained from collector manager", ctx, hostname)); - return; - } - } catch (StatusRuntimeException e) { - if (e.getStatus().getCode() == Status.Code.NOT_FOUND) { - log.error(String.format("%1$s: UtmCollector %2$s could not be deleted because was not found", ctx, hostname)); - return; - } - } - DeleteRequest collectorDelete = DeleteRequest.newBuilder().setDeletedBy(currentUser).build(); - AuthResponse auth = Common.AuthResponse.newBuilder() - .setId(collectorToSearch.get().getId()) - .setKey(collectorToSearch.get().getCollectorKey()) - .build(); - collectorService.deleteCollector(collectorDelete, auth); + var request = getListRequestByHostnameAndModule(hostname, module); + List collectors = getCollectorsByHostnameAndModule(request).getCollectors(); - } catch (Exception e) { - String msg = ctx + ": " + e.getLocalizedMessage(); - log.error(msg); - throw new RuntimeException(msg); + Optional found = collectors.stream().findFirst(); + if (found.isEmpty()) { + log.error("{}: Collector {} not found in Agent Manager", ctx, hostname); + return; } + + CollectorDTO collector = found.get(); + + collectorService.deleteCollector( + collector.getId(), + collector.getCollectorKey() + ); + + log.info("{}: Collector {} deleted successfully", ctx, hostname); + } - public List mapPasswordConfiguration( List configs) { - return configs.stream().peek(config -> { + public List mapPasswordConfiguration(List configs) { + + return configs.stream().peek(config -> { if (config.getConfDataType().equals("password")) { final UtmModuleGroupConfiguration utmModuleGroupConfiguration = utmModuleGroupConfigurationRepository.findById(config.getId()) .orElseThrow(() -> new RuntimeException(String.format("Configuration id %s not found", config.getId()))); - if (config.getConfValue().equals(utmModuleGroupConfiguration.getConfValue())){ + if (config.getConfValue().equals(utmModuleGroupConfiguration.getConfValue())) { config.setConfValue(CipherUtil.decrypt(utmModuleGroupConfiguration.getConfValue(), ENCRYPTION_KEY)); } } - }).collect(Collectors.toList()); + }).collect(Collectors.toList()); } @Transactional @@ -377,6 +322,7 @@ public void updateGroup(List collectorsIds, Long assetGroupId) throws Exce throw new Exception(ctx + ": " + e.getMessage()); } } + @Transactional public Page searchGroupsByFilter(AssetGroupFilter filter, Pageable pageable) throws Exception { final String ctx = CLASSNAME + ".searchGroupsByFilter"; @@ -492,20 +438,20 @@ public void deleteCollector(Long id) throws Exception { this.deleteCollector(collector.get().getHostname(), CollectorModuleEnum.valueOf(collector.get().getModule())); List modules = this.utmModuleGroupRepository.findAllByCollector(id.toString()); - if(!modules.isEmpty()){ + if (!modules.isEmpty()) { UtmModule module = utmModuleRepository.findById(modules.get(0).getModuleId()).get(); - if(module.getModuleActive()){ + if (module.getModuleActive()) { modules = this.utmModuleGroupRepository.findAllByModuleId(module.getId()) - .stream().filter( m -> !m.getCollector().equals(id.toString())) + .stream().filter(m -> !m.getCollector().equals(id.toString())) .toList(); - if(modules.isEmpty()){ + if (modules.isEmpty()) { this.utmModuleService.activateDeactivate(ModuleActivationDTO.builder() - .serverId(module.getServerId()) - .moduleName(module.getModuleName()) - .activationStatus(false) + .serverId(module.getServerId()) + .moduleName(module.getModuleName()) + .activationStatus(false) .build()); } } @@ -528,14 +474,7 @@ public String validateCollectorConfig(CollectorConfigKeysDTO collectorConfig) { } public CollectorConfig cacheCurrentCollectorConfig(CollectorDTO collectorDTO) throws CollectorServiceGrpcException { - return this.getCollectorConfig( - ConfigRequest.newBuilder() - .setModule(CollectorModule.valueOf(collectorDTO.getModule().toString())) - .build(), - AuthResponse.newBuilder() - .setId(collectorDTO.getId()) - .setKey(collectorDTO.getCollectorKey()) - .build()); + return this.getCollectorConfig(collectorDTO); } public void updateCollectorConfigViaGrpc( @@ -552,10 +491,10 @@ public void updateCollectorConfigurationKeys(CollectorConfigKeysDTO collectorCon try { List configs = utmModuleGroupRepository .findAllByModuleIdAndCollector(collectorConfig.getModuleId(), - String.valueOf(collectorConfig.getCollector().getId())); + String.valueOf(collectorConfig.getCollector().getId())); List keys = collectorConfig.getKeys(); - if (CollectionUtils.isEmpty(collectorConfig.getKeys())){ + if (CollectionUtils.isEmpty(collectorConfig.getKeys())) { utmModuleGroupRepository.deleteAll(configs); } else { for (UtmModuleGroupConfiguration key : keys) { @@ -590,12 +529,11 @@ public ListRequest getListRequestByHostnameAndModule(String hostname, CollectorM } else if (module != null) { query = "module.Is=" + module.name(); } - ListRequest request = ListRequest.newBuilder() + return ListRequest.newBuilder() .setPageNumber(1) .setPageSize(1000000) .setSearchQuery(query) .setSortBy("id,desc") .build(); - return request; } } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index fd962beae..441bc476c 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -1,7 +1,6 @@ package com.park.utmstack.web.rest.collectors; import agent.CollectorOuterClass.CollectorConfig; -import agent.Common.ListRequest; import com.park.utmstack.domain.application_events.enums.ApplicationEventType; import com.park.utmstack.domain.application_modules.UtmModuleGroup; import com.park.utmstack.domain.network_scan.AssetGroupFilter; @@ -19,6 +18,7 @@ import com.park.utmstack.service.dto.collectors.dto.ErrorResponse; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.util.ResponseUtil; import com.park.utmstack.web.rest.errors.BadRequestAlertException; import com.park.utmstack.web.rest.errors.InternalServerErrorException; @@ -143,11 +143,6 @@ public ResponseEntity listCollectorsByModule(@Request log.error(msg); applicationEventService.createEvent(msg, ApplicationEventType.ERROR); return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } catch (CollectorServiceGrpcException e) { - String msg = ctx + ": UtmCollector manager is not available or was an error getting the collector list. " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_GATEWAY, msg); } } @@ -208,11 +203,6 @@ public ResponseEntity listCollectorByHostNameAndModul log.error(msg); applicationEventService.createEvent(msg, ApplicationEventType.ERROR); return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } catch (CollectorServiceGrpcException e) { - String msg = ctx + ": UtmCollector manager is not available or was an error getting configuration. " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_GATEWAY, msg); } } diff --git a/backend/src/main/proto/collector.proto b/backend/src/main/proto/collector.proto new file mode 100644 index 000000000..99fd4d551 --- /dev/null +++ b/backend/src/main/proto/collector.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +option go_package = "github.com/utmstack/UTMStack/agent-manager/agent"; +import "common.proto"; + +package agent; + +service CollectorService { + rpc RegisterCollector(RegisterRequest) returns (AuthResponse) {} + rpc DeleteCollector(DeleteRequest) returns (AuthResponse) {} + rpc ListCollector (ListRequest) returns (ListCollectorResponse) {} + rpc CollectorStream(stream CollectorMessages) returns (stream CollectorMessages) {} + rpc GetCollectorConfig (ConfigRequest) returns (CollectorConfig) {} +} + +service PanelCollectorService { + rpc RegisterCollectorConfig(CollectorConfig) returns (ConfigKnowledge) {} +} + +enum CollectorModule{ + AS_400 = 0; + UTMSTACK = 1; +} + +message RegisterRequest { + string ip = 1; + string hostname = 2; + string version = 3; + CollectorModule collector = 4; +} + +message ListCollectorResponse { + repeated Collector rows = 1; + int32 total = 2; +} + +message Collector { + int32 id = 1; + Status status = 2; + string collector_key = 3; + string ip = 4; + string hostname = 5; + string version = 6; + CollectorModule module = 7; + string last_seen = 8; +} + +message CollectorMessages { + oneof stream_message { + CollectorConfig config = 1; + ConfigKnowledge result = 2; + } +} + +message CollectorConfig { + string collector_id = 1; + repeated CollectorConfigGroup groups = 2; + string request_id = 3; +} + +message CollectorConfigGroup { + int32 id = 1; + string group_name = 2; + string group_description = 3; + repeated CollectorGroupConfigurations configurations = 4; + int32 collector_id = 5; +} + +message CollectorGroupConfigurations { + int32 id = 1; + int32 group_id = 2; + string conf_key = 3; + string conf_value = 4; + string conf_name = 5; + string conf_description = 6; + string conf_data_type = 7; + bool conf_required = 8; +} + +message ConfigKnowledge{ + string accepted = 1; + string request_id = 2; +} + +message ConfigRequest { + CollectorModule module = 1; +} From b081edaf963034f525ab93fa79052c9eee5ab521 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 10:10:53 -0600 Subject: [PATCH 014/240] feat: implement gRPC client and service for collector management --- .../main/java/com/park/utmstack/config/GrpcConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 93f6d97bd..9d014b830 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -12,6 +12,7 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; +@Configuration public class GrpcConfiguration { private ManagedChannel channel; From 1d366be15389ef2a1a378b1adc666e52300de79a Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 15:22:03 -0600 Subject: [PATCH 015/240] fix(module.service): return full response body instead of filtering AS_400 module Signed-off-by: Manuel Abascal --- frontend/src/app/app-module/services/module.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/app-module/services/module.service.ts b/frontend/src/app/app-module/services/module.service.ts index e6e8da2c3..11f4fe494 100644 --- a/frontend/src/app/app-module/services/module.service.ts +++ b/frontend/src/app/app-module/services/module.service.ts @@ -83,7 +83,7 @@ export class ModuleService { m.prettyName = m.prettyName + ' GravityZone'; } }); - return response.body.filter(m => m.moduleName !== this.utmModulesEnum.AS_400); + return response.body; }), catchError(error => { console.error(error); From 67e3f71a72227383597fe79f17d8f9d1f329749e Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 11:10:51 -0600 Subject: [PATCH 016/240] feat: implement gRPC client and service for collector management --- .../service/collectors/CollectorOpsService.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 0d3263a5f..78b855cf8 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -7,7 +7,6 @@ import agent.CollectorOuterClass.Collector; import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; -import agent.CollectorOuterClass.ConfigRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -22,7 +21,6 @@ import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.repository.collector.UtmCollectorRepository; -import com.park.utmstack.security.SecurityUtils; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.application_modules.UtmModuleService; import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; @@ -32,8 +30,6 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; -import com.park.utmstack.service.grpc.AuthResponse; -import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; @@ -42,7 +38,6 @@ import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; -import com.utmstack.grpc.service.CollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; @@ -70,7 +65,7 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); - private final GrpcConnection grpcConnection; + private final ManagedChannel channel; private final PanelCollectorServiceClient panelCollectorService; private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; @@ -92,7 +87,7 @@ public class CollectorOpsService { private final CollectorValidatorService collectorValidatorService; - public CollectorOpsService(GrpcConnection grpcConnection, + public CollectorOpsService(ManagedChannel channel, UtmModuleGroupService moduleGroupService, UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository, UtmCollectorRepository utmCollectorRepository, @@ -103,9 +98,9 @@ public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { - this.grpcConnection = grpcConnection; - this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); - this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); + this.channel = channel; + this.panelCollectorService = new PanelCollectorServiceClient(channel); + this.collectorService = new CollectorServiceClient(channel); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; From 81285106fa17c4424d18219a65a732d0ecf3509e Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 12:22:21 -0600 Subject: [PATCH 017/240] feat: remove unused GrpcInternalKeyInterceptor from collector service clients --- .../config/CollectorConfiguration.java | 25 ------------------- .../grpc/client/CollectorServiceClient.java | 8 +----- .../client/PanelCollectorServiceClient.java | 6 +---- .../GrpcInternalKeyInterceptor.java | 25 ------------------- 4 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java delete mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java deleted file mode 100644 index cb37b9f36..000000000 --- a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.config; - -import com.park.utmstack.grpc.connection.GrpcConnection; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class CollectorConfiguration { - - @Value("${grpc.server.address}") - private String serverAddress; - - @Value("${grpc.server.port}") - private Integer serverPort; - - @Bean - public GrpcConnection collectorConnection() { - - GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); - collectorConnection.connect(); - - return collectorConnection; - } -} diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java index 1760c6005..faa50bbc6 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -3,7 +3,6 @@ import agent.CollectorOuterClass; import agent.CollectorServiceGrpc; import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import com.park.utmstack.service.grpc.AuthResponse; import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; @@ -27,12 +26,7 @@ public CollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { String ctx = "CollectorServiceClient.listCollectors"; try { - - CollectorServiceGrpc.CollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.listCollector(request); - + return baseStub.listCollector(request); } catch (StatusRuntimeException e) { log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java index d1f8cd1f3..db0e30e76 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -2,7 +2,6 @@ import agent.CollectorOuterClass; import agent.PanelCollectorServiceGrpc; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; @@ -19,10 +18,7 @@ public PanelCollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { try { - PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.registerCollectorConfig(config); + return baseStub.registerCollectorConfig(config); } catch (StatusRuntimeException e) { throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java deleted file mode 100644 index eb620fd2a..000000000 --- a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.grpc.interceptor; - -import com.park.utmstack.config.Constants; -import io.grpc.*; - -public class GrpcInternalKeyInterceptor implements ClientInterceptor { - - private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); - private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); - - @Override - public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { - - return new ForwardingClientCall.SimpleForwardingClientCall<>( - channel.newCall(methodDescriptor, callOptions)) { - - @Override public void start(Listener responseListener, Metadata headers) { - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - headers.put(INTERNAL_KEY_HEADER, internalKey); - headers.put(TYPE_HEADER, "internal"); - super.start(responseListener, headers); - } }; - } -} From fbddc442d80094ea728c75bdda63eb34ce2bfaca Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 09:55:36 -0600 Subject: [PATCH 018/240] feat: implement gRPC client and service for collector management --- .../config/CollectorConfiguration.java | 15 +- .../utmstack/config/GrpcConfiguration.java | 2 - .../grpc/client/CollectorServiceClient.java | 94 +++++++++ .../client/PanelCollectorServiceClient.java | 38 ++++ .../grpc/connection/GrpcConnection.java | 34 ++++ .../interceptor/CollectorAuthInterceptor.java | 45 +++++ .../GrpcInternalKeyInterceptor.java | 25 +++ .../collectors/CollectorOpsService.java | 178 ++++++------------ .../rest/collectors/UtmCollectorResource.java | 12 +- backend/src/main/proto/collector.proto | 87 +++++++++ 10 files changed, 389 insertions(+), 141 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java create mode 100644 backend/src/main/proto/collector.proto diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java index ec16e1cee..cb37b9f36 100644 --- a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java @@ -1,15 +1,12 @@ package com.park.utmstack.config; -import com.utmstack.grpc.connection.GrpcConnection; -import com.utmstack.grpc.exception.GrpcConnectionException; -import com.utmstack.grpc.jclient.config.interceptors.impl.GrpcEmptyAuthInterceptor; +import com.park.utmstack.grpc.connection.GrpcConnection; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CollectorConfiguration { - private GrpcConnection collectorConnection; @Value("${grpc.server.address}") private String serverAddress; @@ -18,9 +15,11 @@ public class CollectorConfiguration { private Integer serverPort; @Bean - public GrpcConnection collectorConnection() throws GrpcConnectionException { - this.collectorConnection = new GrpcConnection(); - this.collectorConnection.createChannel(serverAddress, serverPort, new GrpcEmptyAuthInterceptor()); - return this.collectorConnection; + public GrpcConnection collectorConnection() { + + GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); + collectorConnection.connect(); + + return collectorConnection; } } diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 8053115cf..93f6d97bd 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -2,7 +2,6 @@ import com.park.utmstack.security.GrpcInterceptor; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyChannelBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; @@ -13,7 +12,6 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; -@Configuration public class GrpcConfiguration { private ManagedChannel channel; diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java new file mode 100644 index 000000000..1760c6005 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -0,0 +1,94 @@ +package com.park.utmstack.grpc.client; + +import agent.CollectorOuterClass; +import agent.CollectorServiceGrpc; +import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; +import com.park.utmstack.service.grpc.AuthResponse; +import com.park.utmstack.service.grpc.DeleteRequest; +import com.park.utmstack.service.grpc.ListRequest; +import com.park.utmstack.util.exceptions.ApiException; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; + +@Slf4j +public class CollectorServiceClient { + + private final ManagedChannel channel; + private final CollectorServiceGrpc.CollectorServiceBlockingStub baseStub; + + public CollectorServiceClient(ManagedChannel channel) { + this.channel = channel; + this.baseStub = CollectorServiceGrpc.newBlockingStub(channel); + } + + public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { + String ctx = "CollectorServiceClient.listCollectors"; + try { + + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.listCollector(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + public AuthResponse deleteCollector(int collectorId, String collectorKey) { + + try { + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors( + new CollectorAuthInterceptor( + String.valueOf(collectorId), + collectorKey + ) + ); + + DeleteRequest request = DeleteRequest.newBuilder() + .setDeletedBy(String.valueOf(collectorId)) + .build(); + + return stub.deleteCollector(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while deleting collector:{}", collectorId, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error deleting collector", collectorId), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + + public CollectorOuterClass.CollectorConfig getCollectorConfig(int collectorId, String collectorKey, CollectorOuterClass.CollectorModule module) { + + try { + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors( + new CollectorAuthInterceptor( + String.valueOf(collectorId), + collectorKey + ) + ); + + CollectorOuterClass.ConfigRequest request = + CollectorOuterClass.ConfigRequest.newBuilder() + .setModule(module) + .build(); + + return stub.getCollectorConfig(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while getting collector:{}", collectorId, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error getting collector config", collectorId), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + public void shutdown() { + channel.shutdown(); + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java new file mode 100644 index 000000000..d1f8cd1f3 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -0,0 +1,38 @@ +package com.park.utmstack.grpc.client; + +import agent.CollectorOuterClass; +import agent.PanelCollectorServiceGrpc; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; + +public class PanelCollectorServiceClient { + + private final ManagedChannel channel; + private final PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub baseStub; + + public PanelCollectorServiceClient(ManagedChannel channel) { + this.channel = channel; + this.baseStub = PanelCollectorServiceGrpc.newBlockingStub(channel); + } + + public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { + + try { + PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.registerCollectorConfig(config); + + } catch (StatusRuntimeException e) { + throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); + } catch (Exception e) { + throw new RuntimeException("Unexpected error inserting collector config: " + e.getMessage(), e); + } + } + + public void shutdown() { + channel.shutdown(); + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java b/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java new file mode 100644 index 000000000..93c42aede --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java @@ -0,0 +1,34 @@ +package com.park.utmstack.grpc.connection; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class GrpcConnection { + + private ManagedChannel channel; + private final String host; + private final int port; + + public void connect() { + this.channel = ManagedChannelBuilder + .forAddress(host, port) + .usePlaintext() + .build(); + } + + public ManagedChannel getChannel() { + if (channel == null) { + throw new IllegalStateException("Channel not initialized. Call connect() first."); + } + return channel; + } + + public void shutdown() { + if (channel != null && !channel.isShutdown()) { + channel.shutdown(); + } + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java new file mode 100644 index 000000000..701a568fb --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java @@ -0,0 +1,45 @@ +package com.park.utmstack.grpc.interceptor; + +import io.grpc.*; + +public class CollectorAuthInterceptor implements ClientInterceptor { + + private static final Metadata.Key ID_HEADER = + Metadata.Key.of("id", Metadata.ASCII_STRING_MARSHALLER); + + private static final Metadata.Key KEY_HEADER = + Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER); + + private static final Metadata.Key TYPE_HEADER = + Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); + + private final String collectorId; + private final String collectorKey; + + public CollectorAuthInterceptor(String collectorId, String collectorKey) { + this.collectorId = collectorId; + this.collectorKey = collectorKey; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, + CallOptions callOptions, + Channel channel) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>( + channel.newCall(methodDescriptor, callOptions)) { + + @Override + public void start(Listener responseListener, Metadata headers) { + + headers.put(ID_HEADER, collectorId); + headers.put(KEY_HEADER, collectorKey); + headers.put(TYPE_HEADER, "collector"); + + super.start(responseListener, headers); + } + }; + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java new file mode 100644 index 000000000..eb620fd2a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java @@ -0,0 +1,25 @@ +package com.park.utmstack.grpc.interceptor; + +import com.park.utmstack.config.Constants; +import io.grpc.*; + +public class GrpcInternalKeyInterceptor implements ClientInterceptor { + + private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); + + @Override + public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>( + channel.newCall(methodDescriptor, callOptions)) { + + @Override public void start(Listener responseListener, Metadata headers) { + String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); + + headers.put(INTERNAL_KEY_HEADER, internalKey); + headers.put(TYPE_HEADER, "internal"); + super.start(responseListener, headers); + } }; + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index a18fae336..0d3263a5f 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -8,10 +8,6 @@ import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; import agent.CollectorOuterClass.ConfigRequest; -import agent.Common; -import agent.Common.ListRequest; -import agent.Common.AuthResponse; -import agent.Common.DeleteRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -19,6 +15,9 @@ import com.park.utmstack.domain.collector.UtmCollector; import com.park.utmstack.domain.network_scan.AssetGroupFilter; import com.park.utmstack.domain.network_scan.UtmAssetGroup; +import com.park.utmstack.grpc.client.CollectorServiceClient; +import com.park.utmstack.grpc.client.PanelCollectorServiceClient; +import com.park.utmstack.grpc.connection.GrpcConnection; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; @@ -33,21 +32,24 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.grpc.AuthResponse; +import com.park.utmstack.service.grpc.DeleteRequest; +import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; +import com.park.utmstack.util.exceptions.ApiException; import com.park.utmstack.web.rest.errors.BadRequestAlertException; -import com.utmstack.grpc.connection.GrpcConnection; import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; import com.utmstack.grpc.service.CollectorService; -import com.utmstack.grpc.service.PanelCollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -69,8 +71,8 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); private final GrpcConnection grpcConnection; - private final PanelCollectorService panelCollectorService; - private final CollectorService collectorService; + private final PanelCollectorServiceClient panelCollectorService; + private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; private final UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository; @@ -100,9 +102,10 @@ public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleService utmModuleService, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { + this.grpcConnection = grpcConnection; - this.panelCollectorService = new PanelCollectorService(grpcConnection); - this.collectorService = new CollectorService(grpcConnection); + this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); + this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; @@ -130,35 +133,15 @@ public ConfigKnowledge upsertCollectorConfig(CollectorConfig config) throws Coll } try { - return panelCollectorService.insertCollectorConfig(config, internalKey); + return panelCollectorService.insertCollectorConfig(config); } catch (Exception e) { String msg = ctx + ": " + e.getMessage(); throw new CollectorConfigurationGrpcException(msg); } } - /** - * Method to get collectors list. - * - * @param request is the request with all the pagination and search params used to list collectors - * according to those params. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. - */ - public ListCollectorsResponseDTO listCollector(ListRequest request) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".listCollector"; - - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - if (!StringUtils.hasText(internalKey)) { - throw new BadRequestAlertException(ctx + ": Internal key not configured.", ctx, CLASSNAME); - } - - try { - return mapToListCollectorsResponseDTO(collectorService.listCollector(request, internalKey)); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public ListCollectorsResponseDTO listCollector(ListRequest request) { + return mapToListCollectorsResponseDTO(collectorService.listCollectors(request)); } /** @@ -178,10 +161,10 @@ public CollectorHostnames listCollectorHostnames(ListRequest request) throws Col } try { - ListCollectorResponse response = collectorService.listCollector(request, internalKey); + ListCollectorResponse response = collectorService.listCollectors(request); CollectorHostnames collectorHostnames = new CollectorHostnames(); - response.getRowsList().forEach(c->{ + response.getRowsList().forEach(c -> { collectorHostnames.getHostname().add(c.getHostname()); }); @@ -196,48 +179,20 @@ public CollectorHostnames listCollectorHostnames(ListRequest request) throws Col * Method to get collectors by hostname and module. * * @param request contains the filter information used to search. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. */ - public ListCollectorsResponseDTO getCollectorsByHostnameAndModule(ListRequest request) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".GetCollectorsByHostnameAndModule"; - - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - if (!StringUtils.hasText(internalKey)) { - throw new BadRequestAlertException(ctx + ": Internal key not configured.", ctx, CLASSNAME); - } - - try { - return mapToListCollectorsResponseDTO(collectorService.listCollector(request, internalKey)); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public ListCollectorsResponseDTO getCollectorsByHostnameAndModule(ListRequest request) { + return mapToListCollectorsResponseDTO(collectorService.listCollectors(request)); } - /** - * Method to get a collector config from agent manager via gRPC. - * - * @param request represents the CollectorModule to get the configurations from. - * @param auth is the authentication parameters used to filter in order to get the collector configuration. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. - */ - public CollectorConfig getCollectorConfig(ConfigRequest request, AuthResponse auth) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".getCollectorConfig"; - - - try { - return collectorService.requestCollectorConfig(request, auth); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public CollectorConfig getCollectorConfig(CollectorDTO collectorDTO) { + return collectorService.getCollectorConfig(collectorDTO.getId(), collectorDTO.getCollectorKey(), + CollectorModule.valueOf(collectorDTO.getModule().toString())); } /** * Method to transform a ListCollectorResponse to ListCollectorsResponseDTO */ - private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorResponse response) throws Exception { + private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorResponse response) { final String ctx = CLASSNAME + ".mapToListCollectorsResponseDTO"; try { ListCollectorsResponseDTO dto = new ListCollectorsResponseDTO(); @@ -253,7 +208,7 @@ private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorRe return dto; } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); + throw new ApiException(String.format("%s: Error mapping ListCollectorResponse to ListCollectorsResponseDTO: %s", ctx, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -321,50 +276,40 @@ private CollectorGroupConfigurations mapToCollectorGroupConfigurations(UtmModule */ public void deleteCollector(String hostname, CollectorModuleEnum module) { final String ctx = CLASSNAME + ".deleteCollector"; - try { - String currentUser = SecurityUtils.getCurrentUserLogin().orElseThrow(() -> new RuntimeException("No current user login")); - - Optional collectorToSearch = getCollectorsByHostnameAndModule( - getListRequestByHostnameAndModule(hostname, module)).getCollectors() - .stream().findFirst(); - try { - if (collectorToSearch.isEmpty()) { - log.error(String.format("%1$s: UtmCollector %2$s could not be deleted because no information was obtained from collector manager", ctx, hostname)); - return; - } - } catch (StatusRuntimeException e) { - if (e.getStatus().getCode() == Status.Code.NOT_FOUND) { - log.error(String.format("%1$s: UtmCollector %2$s could not be deleted because was not found", ctx, hostname)); - return; - } - } - DeleteRequest collectorDelete = DeleteRequest.newBuilder().setDeletedBy(currentUser).build(); - AuthResponse auth = Common.AuthResponse.newBuilder() - .setId(collectorToSearch.get().getId()) - .setKey(collectorToSearch.get().getCollectorKey()) - .build(); - collectorService.deleteCollector(collectorDelete, auth); + var request = getListRequestByHostnameAndModule(hostname, module); + List collectors = getCollectorsByHostnameAndModule(request).getCollectors(); - } catch (Exception e) { - String msg = ctx + ": " + e.getLocalizedMessage(); - log.error(msg); - throw new RuntimeException(msg); + Optional found = collectors.stream().findFirst(); + if (found.isEmpty()) { + log.error("{}: Collector {} not found in Agent Manager", ctx, hostname); + return; } + + CollectorDTO collector = found.get(); + + collectorService.deleteCollector( + collector.getId(), + collector.getCollectorKey() + ); + + log.info("{}: Collector {} deleted successfully", ctx, hostname); + } - public List mapPasswordConfiguration( List configs) { - return configs.stream().peek(config -> { + public List mapPasswordConfiguration(List configs) { + + return configs.stream().peek(config -> { if (config.getConfDataType().equals("password")) { final UtmModuleGroupConfiguration utmModuleGroupConfiguration = utmModuleGroupConfigurationRepository.findById(config.getId()) .orElseThrow(() -> new RuntimeException(String.format("Configuration id %s not found", config.getId()))); - if (config.getConfValue().equals(utmModuleGroupConfiguration.getConfValue())){ + if (config.getConfValue().equals(utmModuleGroupConfiguration.getConfValue())) { config.setConfValue(CipherUtil.decrypt(utmModuleGroupConfiguration.getConfValue(), ENCRYPTION_KEY)); } } - }).collect(Collectors.toList()); + }).collect(Collectors.toList()); } @Transactional @@ -377,6 +322,7 @@ public void updateGroup(List collectorsIds, Long assetGroupId) throws Exce throw new Exception(ctx + ": " + e.getMessage()); } } + @Transactional public Page searchGroupsByFilter(AssetGroupFilter filter, Pageable pageable) throws Exception { final String ctx = CLASSNAME + ".searchGroupsByFilter"; @@ -492,20 +438,20 @@ public void deleteCollector(Long id) throws Exception { this.deleteCollector(collector.get().getHostname(), CollectorModuleEnum.valueOf(collector.get().getModule())); List modules = this.utmModuleGroupRepository.findAllByCollector(id.toString()); - if(!modules.isEmpty()){ + if (!modules.isEmpty()) { UtmModule module = utmModuleRepository.findById(modules.get(0).getModuleId()).get(); - if(module.getModuleActive()){ + if (module.getModuleActive()) { modules = this.utmModuleGroupRepository.findAllByModuleId(module.getId()) - .stream().filter( m -> !m.getCollector().equals(id.toString())) + .stream().filter(m -> !m.getCollector().equals(id.toString())) .toList(); - if(modules.isEmpty()){ + if (modules.isEmpty()) { this.utmModuleService.activateDeactivate(ModuleActivationDTO.builder() - .serverId(module.getServerId()) - .moduleName(module.getModuleName()) - .activationStatus(false) + .serverId(module.getServerId()) + .moduleName(module.getModuleName()) + .activationStatus(false) .build()); } } @@ -528,14 +474,7 @@ public String validateCollectorConfig(CollectorConfigKeysDTO collectorConfig) { } public CollectorConfig cacheCurrentCollectorConfig(CollectorDTO collectorDTO) throws CollectorServiceGrpcException { - return this.getCollectorConfig( - ConfigRequest.newBuilder() - .setModule(CollectorModule.valueOf(collectorDTO.getModule().toString())) - .build(), - AuthResponse.newBuilder() - .setId(collectorDTO.getId()) - .setKey(collectorDTO.getCollectorKey()) - .build()); + return this.getCollectorConfig(collectorDTO); } public void updateCollectorConfigViaGrpc( @@ -552,10 +491,10 @@ public void updateCollectorConfigurationKeys(CollectorConfigKeysDTO collectorCon try { List configs = utmModuleGroupRepository .findAllByModuleIdAndCollector(collectorConfig.getModuleId(), - String.valueOf(collectorConfig.getCollector().getId())); + String.valueOf(collectorConfig.getCollector().getId())); List keys = collectorConfig.getKeys(); - if (CollectionUtils.isEmpty(collectorConfig.getKeys())){ + if (CollectionUtils.isEmpty(collectorConfig.getKeys())) { utmModuleGroupRepository.deleteAll(configs); } else { for (UtmModuleGroupConfiguration key : keys) { @@ -590,12 +529,11 @@ public ListRequest getListRequestByHostnameAndModule(String hostname, CollectorM } else if (module != null) { query = "module.Is=" + module.name(); } - ListRequest request = ListRequest.newBuilder() + return ListRequest.newBuilder() .setPageNumber(1) .setPageSize(1000000) .setSearchQuery(query) .setSortBy("id,desc") .build(); - return request; } } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index fd962beae..441bc476c 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -1,7 +1,6 @@ package com.park.utmstack.web.rest.collectors; import agent.CollectorOuterClass.CollectorConfig; -import agent.Common.ListRequest; import com.park.utmstack.domain.application_events.enums.ApplicationEventType; import com.park.utmstack.domain.application_modules.UtmModuleGroup; import com.park.utmstack.domain.network_scan.AssetGroupFilter; @@ -19,6 +18,7 @@ import com.park.utmstack.service.dto.collectors.dto.ErrorResponse; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.util.ResponseUtil; import com.park.utmstack.web.rest.errors.BadRequestAlertException; import com.park.utmstack.web.rest.errors.InternalServerErrorException; @@ -143,11 +143,6 @@ public ResponseEntity listCollectorsByModule(@Request log.error(msg); applicationEventService.createEvent(msg, ApplicationEventType.ERROR); return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } catch (CollectorServiceGrpcException e) { - String msg = ctx + ": UtmCollector manager is not available or was an error getting the collector list. " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_GATEWAY, msg); } } @@ -208,11 +203,6 @@ public ResponseEntity listCollectorByHostNameAndModul log.error(msg); applicationEventService.createEvent(msg, ApplicationEventType.ERROR); return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } catch (CollectorServiceGrpcException e) { - String msg = ctx + ": UtmCollector manager is not available or was an error getting configuration. " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_GATEWAY, msg); } } diff --git a/backend/src/main/proto/collector.proto b/backend/src/main/proto/collector.proto new file mode 100644 index 000000000..99fd4d551 --- /dev/null +++ b/backend/src/main/proto/collector.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +option go_package = "github.com/utmstack/UTMStack/agent-manager/agent"; +import "common.proto"; + +package agent; + +service CollectorService { + rpc RegisterCollector(RegisterRequest) returns (AuthResponse) {} + rpc DeleteCollector(DeleteRequest) returns (AuthResponse) {} + rpc ListCollector (ListRequest) returns (ListCollectorResponse) {} + rpc CollectorStream(stream CollectorMessages) returns (stream CollectorMessages) {} + rpc GetCollectorConfig (ConfigRequest) returns (CollectorConfig) {} +} + +service PanelCollectorService { + rpc RegisterCollectorConfig(CollectorConfig) returns (ConfigKnowledge) {} +} + +enum CollectorModule{ + AS_400 = 0; + UTMSTACK = 1; +} + +message RegisterRequest { + string ip = 1; + string hostname = 2; + string version = 3; + CollectorModule collector = 4; +} + +message ListCollectorResponse { + repeated Collector rows = 1; + int32 total = 2; +} + +message Collector { + int32 id = 1; + Status status = 2; + string collector_key = 3; + string ip = 4; + string hostname = 5; + string version = 6; + CollectorModule module = 7; + string last_seen = 8; +} + +message CollectorMessages { + oneof stream_message { + CollectorConfig config = 1; + ConfigKnowledge result = 2; + } +} + +message CollectorConfig { + string collector_id = 1; + repeated CollectorConfigGroup groups = 2; + string request_id = 3; +} + +message CollectorConfigGroup { + int32 id = 1; + string group_name = 2; + string group_description = 3; + repeated CollectorGroupConfigurations configurations = 4; + int32 collector_id = 5; +} + +message CollectorGroupConfigurations { + int32 id = 1; + int32 group_id = 2; + string conf_key = 3; + string conf_value = 4; + string conf_name = 5; + string conf_description = 6; + string conf_data_type = 7; + bool conf_required = 8; +} + +message ConfigKnowledge{ + string accepted = 1; + string request_id = 2; +} + +message ConfigRequest { + CollectorModule module = 1; +} From cee14f3208f0d6de24102c384257f41cda823f1c Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 10:10:53 -0600 Subject: [PATCH 019/240] feat: implement gRPC client and service for collector management --- .../main/java/com/park/utmstack/config/GrpcConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 93f6d97bd..9d014b830 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -12,6 +12,7 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; +@Configuration public class GrpcConfiguration { private ManagedChannel channel; From 6cfc0954ac98f8936d3cc82bfaffc3d0a4afe905 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 11:10:51 -0600 Subject: [PATCH 020/240] feat: implement gRPC client and service for collector management --- .../service/collectors/CollectorOpsService.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 0d3263a5f..78b855cf8 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -7,7 +7,6 @@ import agent.CollectorOuterClass.Collector; import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; -import agent.CollectorOuterClass.ConfigRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -22,7 +21,6 @@ import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.repository.collector.UtmCollectorRepository; -import com.park.utmstack.security.SecurityUtils; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.application_modules.UtmModuleService; import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; @@ -32,8 +30,6 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; -import com.park.utmstack.service.grpc.AuthResponse; -import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; @@ -42,7 +38,6 @@ import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; -import com.utmstack.grpc.service.CollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; @@ -70,7 +65,7 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); - private final GrpcConnection grpcConnection; + private final ManagedChannel channel; private final PanelCollectorServiceClient panelCollectorService; private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; @@ -92,7 +87,7 @@ public class CollectorOpsService { private final CollectorValidatorService collectorValidatorService; - public CollectorOpsService(GrpcConnection grpcConnection, + public CollectorOpsService(ManagedChannel channel, UtmModuleGroupService moduleGroupService, UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository, UtmCollectorRepository utmCollectorRepository, @@ -103,9 +98,9 @@ public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { - this.grpcConnection = grpcConnection; - this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); - this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); + this.channel = channel; + this.panelCollectorService = new PanelCollectorServiceClient(channel); + this.collectorService = new CollectorServiceClient(channel); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; From 9dfa0a8978a3b96633a700c0d0dc8c3aec7cdd19 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 12:22:21 -0600 Subject: [PATCH 021/240] feat: remove unused GrpcInternalKeyInterceptor from collector service clients --- .../config/CollectorConfiguration.java | 25 ------------------- .../grpc/client/CollectorServiceClient.java | 8 +----- .../client/PanelCollectorServiceClient.java | 6 +---- .../GrpcInternalKeyInterceptor.java | 25 ------------------- 4 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java delete mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java deleted file mode 100644 index cb37b9f36..000000000 --- a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.config; - -import com.park.utmstack.grpc.connection.GrpcConnection; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class CollectorConfiguration { - - @Value("${grpc.server.address}") - private String serverAddress; - - @Value("${grpc.server.port}") - private Integer serverPort; - - @Bean - public GrpcConnection collectorConnection() { - - GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); - collectorConnection.connect(); - - return collectorConnection; - } -} diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java index 1760c6005..faa50bbc6 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -3,7 +3,6 @@ import agent.CollectorOuterClass; import agent.CollectorServiceGrpc; import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import com.park.utmstack.service.grpc.AuthResponse; import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; @@ -27,12 +26,7 @@ public CollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { String ctx = "CollectorServiceClient.listCollectors"; try { - - CollectorServiceGrpc.CollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.listCollector(request); - + return baseStub.listCollector(request); } catch (StatusRuntimeException e) { log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java index d1f8cd1f3..db0e30e76 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -2,7 +2,6 @@ import agent.CollectorOuterClass; import agent.PanelCollectorServiceGrpc; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; @@ -19,10 +18,7 @@ public PanelCollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { try { - PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.registerCollectorConfig(config); + return baseStub.registerCollectorConfig(config); } catch (StatusRuntimeException e) { throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java deleted file mode 100644 index eb620fd2a..000000000 --- a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.grpc.interceptor; - -import com.park.utmstack.config.Constants; -import io.grpc.*; - -public class GrpcInternalKeyInterceptor implements ClientInterceptor { - - private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); - private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); - - @Override - public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { - - return new ForwardingClientCall.SimpleForwardingClientCall<>( - channel.newCall(methodDescriptor, callOptions)) { - - @Override public void start(Listener responseListener, Metadata headers) { - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - headers.put(INTERNAL_KEY_HEADER, internalKey); - headers.put(TYPE_HEADER, "internal"); - super.start(responseListener, headers); - } }; - } -} From 7dde52e75de629e6da87bb22a18e703217f58ba5 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 13:41:11 -0600 Subject: [PATCH 022/240] feat: add CollectorConfigDTO and unique server name validation --- .../validators/UniqueServerName.java | 18 +++++++++ .../validators/UniqueServerNameValidator.java | 27 +++++++++++++ .../collectors/dto/CollectorConfigDTO.java | 27 +++++++++++++ .../dto/CollectorConfigKeysDTO.java | 39 ------------------- 4 files changed, 72 insertions(+), 39 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java create mode 100644 backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java create mode 100644 backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java delete mode 100644 backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java diff --git a/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java new file mode 100644 index 000000000..de04fc86a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java @@ -0,0 +1,18 @@ +package com.park.utmstack.domain.collector.validators; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = UniqueServerNameValidator.class) +public @interface UniqueServerName { + String message() default "Server name must be unique."; + Class[] groups() default {}; + Class[] payload() default {}; +} + diff --git a/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java new file mode 100644 index 000000000..437fcbcd1 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java @@ -0,0 +1,27 @@ +package com.park.utmstack.domain.collector.validators; + +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; +import java.util.stream.Collectors; + +public class UniqueServerNameValidator implements ConstraintValidator> { + + @Override + public boolean isValid(List keys, ConstraintValidatorContext context) { + + if (keys == null || keys.isEmpty()) return false; + + long duplicates = keys.stream() + .filter(k -> "Hostname".equals(k.getConfName())) + .collect(Collectors.groupingBy(UtmModuleGroupConfiguration::getConfValue, Collectors.counting())) + .values().stream() + .filter(count -> count > 1) + .count(); + + return duplicates == 0; + + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java new file mode 100644 index 000000000..562d1bad6 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java @@ -0,0 +1,27 @@ +package com.park.utmstack.service.dto.collectors.dto; + +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; +import com.park.utmstack.domain.collector.validators.UniqueServerName; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Setter +@Getter +public class CollectorConfigDTO { + + @NotNull + CollectorDTO collector; + + @NotNull + private Long moduleId; + + @NotNull + @NotEmpty + @UniqueServerName + private List keys; + +} diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java deleted file mode 100644 index 40af52707..000000000 --- a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.park.utmstack.service.dto.collectors.dto; - -import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; - -import javax.validation.constraints.NotNull; -import java.util.List; - -public class CollectorConfigKeysDTO { - @NotNull - CollectorDTO collector; - @NotNull - private Long moduleId; - @NotNull - private List keys; - - public Long getModuleId() { - return moduleId; - } - - public void setModuleId(Long moduleId) { - this.moduleId = moduleId; - } - - public List getKeys() { - return keys; - } - - public void setKeys(List keys) { - this.keys = keys; - } - - public CollectorDTO getCollector() { - return collector; - } - - public void setCollector(CollectorDTO collector) { - this.collector = collector; - } -} From 230840b2b583ed2fa698914a8084c47e3d0a8d23 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:17:10 -0600 Subject: [PATCH 023/240] feat: add CollectorConfigBuilder for constructing CollectorConfig from DTO --- .../collectors/CollectorConfigBuilder.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java new file mode 100644 index 000000000..cd821aa30 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java @@ -0,0 +1,108 @@ +package com.park.utmstack.service.collectors; + +import agent.CollectorOuterClass.CollectorConfig; +import agent.CollectorOuterClass.CollectorConfigGroup; +import agent.CollectorOuterClass.CollectorGroupConfigurations; +import com.park.utmstack.config.Constants; +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; +import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; +import com.park.utmstack.service.application_modules.UtmModuleGroupService; +import com.park.utmstack.util.CipherUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Component +@RequiredArgsConstructor +public class CollectorConfigBuilder { + + private final UtmModuleGroupService moduleGroupService; + private final UtmModuleGroupConfigurationRepository configRepo; + + public CollectorConfig build(CollectorConfigDTO dto) { + + List processed = processPasswords(dto.getKeys()); + + return buildCollectorConfig(processed, dto.getCollector()); + } + + + private List processPasswords(List configs) { + + return configs.stream().map(config -> { + + if (Constants.CONF_TYPE_PASSWORD.equals(config.getConfDataType())) { + + UtmModuleGroupConfiguration original = configRepo.findById(config.getId()) + .orElseThrow(() -> new RuntimeException("Configuration id " + config.getId() + " not found")); + + if (Objects.equals(config.getConfValue(), original.getConfValue())) { + config.setConfValue( + CipherUtil.decrypt(original.getConfValue(), System.getenv(Constants.ENV_ENCRYPTION_KEY)) + ); + } + } + + return config; + + }).toList(); + } + + + private CollectorConfig buildCollectorConfig(List keys, CollectorDTO collectorDTO) { + + List groupIds = keys.stream() + .map(UtmModuleGroupConfiguration::getGroupId) + .distinct() + .toList(); + + List groups = new ArrayList<>(); + + for (Long groupId : groupIds) { + + moduleGroupService.findOne(groupId).ifPresent(group -> { + + List configs = + keys.stream() + .filter(k -> k.getGroupId().equals(groupId)) + .map(this::mapToCollectorGroupConfigurations) + .toList(); + + groups.add( + CollectorConfigGroup.newBuilder() + .setGroupName(group.getGroupName()) + .setGroupDescription(group.getGroupDescription()) + .addAllConfigurations(configs) + .setCollectorId(collectorDTO.getId()) + .build() + ); + }); + } + + return CollectorConfig.newBuilder() + .setCollectorId(String.valueOf(collectorDTO.getId())) + .setRequestId(String.valueOf(System.currentTimeMillis())) + .addAllGroups(groups) + .build(); + } + + + private CollectorGroupConfigurations mapToCollectorGroupConfigurations( + UtmModuleGroupConfiguration c) { + + return CollectorGroupConfigurations.newBuilder() + .setConfKey(c.getConfKey()) + .setConfName(c.getConfName()) + .setConfDescription(c.getConfDescription()) + .setConfDataType(c.getConfDataType()) + .setConfValue(c.getConfValue()) + .setConfRequired(c.getConfRequired()) + .build(); + } +} + From 40e853c71d6718cab9acb74b1d7e31b6b434a3c7 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:17:34 -0600 Subject: [PATCH 024/240] feat: add CollectorGrpcService for managing collector operations via gRPC --- .../collectors/CollectorGrpcService.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java new file mode 100644 index 000000000..073900217 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java @@ -0,0 +1,36 @@ +package com.park.utmstack.service.collectors; + +import agent.CollectorOuterClass.*; +import com.park.utmstack.grpc.client.CollectorServiceClient; +import com.park.utmstack.grpc.client.PanelCollectorServiceClient; +import com.park.utmstack.service.grpc.ListRequest; +import io.grpc.ManagedChannel; +import org.springframework.stereotype.Service; + +@Service +public class CollectorGrpcService { + + private final CollectorServiceClient collectorClient; + private final PanelCollectorServiceClient panelClient; + + public CollectorGrpcService(ManagedChannel channel) { + this.collectorClient = new CollectorServiceClient(channel); + this.panelClient = new PanelCollectorServiceClient(channel); + } + + public ListCollectorResponse listCollectors(ListRequest request) { + return collectorClient.listCollectors(request); + } + + public CollectorConfig getCollectorConfig(int id, String key, CollectorModule module) { + return collectorClient.getCollectorConfig(id, key, module); + } + + public void deleteCollector(int id, String key) { + collectorClient.deleteCollector(id, key); + } + + public ConfigKnowledge upsertCollectorConfig(CollectorConfig config) { + return panelClient.insertCollectorConfig(config); + } +} From 39820b74d01f9215767255b15ab89813019cc62b Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:05 -0600 Subject: [PATCH 025/240] feat: update CollectorConfig validation and add CollectorService for gRPC integration --- .../collectors/CollectorOpsService.java | 9 +++--- .../service/collectors/CollectorService.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 78b855cf8..df0ef0452 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -16,7 +16,6 @@ import com.park.utmstack.domain.network_scan.UtmAssetGroup; import com.park.utmstack.grpc.client.CollectorServiceClient; import com.park.utmstack.grpc.client.PanelCollectorServiceClient; -import com.park.utmstack.grpc.connection.GrpcConnection; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; @@ -26,7 +25,7 @@ import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; import com.park.utmstack.service.dto.collectors.CollectorHostnames; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; @@ -458,7 +457,7 @@ public void deleteCollector(Long id) throws Exception { } - public String validateCollectorConfig(CollectorConfigKeysDTO collectorConfig) { + public String validateCollectorConfig(CollectorConfigDTO collectorConfig) { Errors errors = new BeanPropertyBindingResult(collectorConfig, "updateConfigurationKeysBody"); collectorValidatorService.validate(collectorConfig, errors); @@ -473,7 +472,7 @@ public CollectorConfig cacheCurrentCollectorConfig(CollectorDTO collectorDTO) th } public void updateCollectorConfigViaGrpc( - CollectorConfigKeysDTO collectorConfig, + CollectorConfigDTO collectorConfig, CollectorDTO collectorDTO) throws CollectorConfigurationGrpcException { this.upsertCollectorConfig( @@ -481,7 +480,7 @@ public void updateCollectorConfigViaGrpc( this.mapPasswordConfiguration(collectorConfig.getKeys()), collectorDTO)); } - public void updateCollectorConfigurationKeys(CollectorConfigKeysDTO collectorConfig) throws Exception { + public void updateCollectorConfigurationKeys(CollectorConfigDTO collectorConfig) throws Exception { final String ctx = CLASSNAME + ".updateCollectorConfigurationKeys"; try { List configs = utmModuleGroupRepository diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java new file mode 100644 index 000000000..bbffd535a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java @@ -0,0 +1,28 @@ +package com.park.utmstack.service.collectors; + +import agent.CollectorOuterClass; +import com.park.utmstack.service.application_events.ApplicationEventService; +import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CollectorService { + + private final CollectorGrpcService collectorGrpcService; + private final ApplicationEventService eventService; + private final UtmModuleGroupConfigurationService moduleGroupConfigurationService; + private final CollectorConfigBuilder CollectorConfigBuilder; + + public void upsertCollectorConfig(CollectorConfigDTO collectorConfig) { + + this.moduleGroupConfigurationService.updateConfigurationKeys(collectorConfig.getModuleId(), collectorConfig.getKeys()); + + CollectorOuterClass.CollectorConfig collector = CollectorConfigBuilder.build(collectorConfig); + collectorGrpcService.upsertCollectorConfig(collector); + } + +} + From 8edb406591320c3989f712d920ba736119604499 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:29 -0600 Subject: [PATCH 026/240] feat: update CollectorValidatorService to use CollectorConfigDTO for validation --- .../validators/collector/CollectorValidatorService.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java b/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java index ceaccbfaf..02d1bca1b 100644 --- a/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java +++ b/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java @@ -1,8 +1,7 @@ package com.park.utmstack.service.validators.collector; import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; -import com.park.utmstack.web.rest.application_modules.UtmModuleGroupConfigurationResource; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import org.springframework.stereotype.Service; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -14,12 +13,12 @@ public class CollectorValidatorService implements Validator { @Override public boolean supports(Class clazz) { - return CollectorConfigKeysDTO.class.equals(clazz); + return CollectorConfigDTO.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { - CollectorConfigKeysDTO updateConfigurationKeysBody = (CollectorConfigKeysDTO) target; + CollectorConfigDTO updateConfigurationKeysBody = (CollectorConfigDTO) target; Map hostNames = updateConfigurationKeysBody.getKeys().stream() .filter(config -> config.getConfName().equals("Hostname")) From 63c9d284e5ccda39b0a5e8f5067c69d340203f3c Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:34 -0600 Subject: [PATCH 027/240] feat: refactor UtmCollectorResource to use CollectorConfigDTO and CollectorOpsService --- .../rest/collectors/UtmCollectorResource.java | 77 ++++++------------- 1 file changed, 24 insertions(+), 53 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index 441bc476c..f6dfc8303 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -9,10 +9,11 @@ import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.collectors.CollectorOpsService; +import com.park.utmstack.service.collectors.CollectorService; import com.park.utmstack.service.collectors.UtmCollectorService; import com.park.utmstack.service.dto.collectors.CollectorActionEnum; import com.park.utmstack.service.dto.collectors.CollectorHostnames; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; import com.park.utmstack.service.dto.collectors.dto.ErrorResponse; @@ -27,6 +28,7 @@ import com.park.utmstack.web.rest.util.PaginationUtil; import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; +import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springdoc.api.annotations.ParameterObject; @@ -48,35 +50,20 @@ * REST controller for managing {@link UtmCollectorResource}. */ @RestController +@RequiredArgsConstructor @RequestMapping("/api") public class UtmCollectorResource { private static final String CLASSNAME = "UtmCollectorResource"; - private final CollectorOpsService collectorService; + private final CollectorOpsService collectorOpsService; private final Logger log = LoggerFactory.getLogger(UtmCollectorResource.class); private final ApplicationEventService applicationEventService; private final UtmModuleGroupConfigurationService moduleGroupConfigurationService; - private final UtmModuleGroupService moduleGroupService; - private final ApplicationEventService eventService; - private final UtmCollectorService utmCollectorService; + private final CollectorService collectorService; - public UtmCollectorResource(CollectorOpsService collectorService, - ApplicationEventService applicationEventService, - UtmModuleGroupConfigurationService moduleGroupConfigurationService, - UtmModuleGroupService moduleGroupService, - ApplicationEventService eventService, - UtmCollectorService utmCollectorService) { - - this.collectorService = collectorService; - this.applicationEventService = applicationEventService; - this.moduleGroupConfigurationService = moduleGroupConfigurationService; - this.moduleGroupService = moduleGroupService; - this.eventService = eventService; - this.utmCollectorService = utmCollectorService; - } /** * {@code POST /collector-config} : Create or update the collector configs. @@ -87,27 +74,11 @@ public UtmCollectorResource(CollectorOpsService collectorService, * persist the configurations. */ @PostMapping("/collector-config") - public ResponseEntity upsertCollectorConfig( - @Valid @RequestBody CollectorConfigKeysDTO collectorConfig, - @RequestParam(name = "action", defaultValue = "CREATE") CollectorActionEnum action) { - - final String ctx = CLASSNAME + ".upsertCollectorConfig"; - CollectorConfig cacheConfig = null; + public ResponseEntity upsertCollectorConfig(@Valid @RequestBody CollectorConfigDTO collectorConfig, + @RequestParam(name = "action", defaultValue = "CREATE") CollectorActionEnum action) { - // Validate collector configuration - String validationErrorMessage = this.collectorService.validateCollectorConfig(collectorConfig); - if (validationErrorMessage != null) { - return logAndResponse(new ErrorResponse(validationErrorMessage, HttpStatus.PRECONDITION_FAILED)); - } - - try { - cacheConfig = this.collectorService.cacheCurrentCollectorConfig(collectorConfig.getCollector()); - this.upsert(collectorConfig); - return ResponseEntity.noContent().build(); - - } catch (Exception e) { - return handleUpdateError(e, cacheConfig, collectorConfig.getCollector()); - } + collectorService.upsertCollectorConfig(collectorConfig); + return ResponseEntity.noContent().build(); } /** @@ -134,7 +105,7 @@ public ResponseEntity listCollectorsByModule(@Request .setSortBy(sortBy != null ? sortBy : "") .build(); - ListCollectorsResponseDTO response = collectorService.listCollector(request); + ListCollectorsResponseDTO response = collectorOpsService.listCollector(request); HttpHeaders headers = new HttpHeaders(); headers.add("X-Total-Count", Long.toString(response.getTotal())); return ResponseEntity.ok().headers(headers).body(response); @@ -169,7 +140,7 @@ public ResponseEntity listCollectorHostNames(@RequestParam(r .setSearchQuery(module != null ? "module.Is=" + module : "") .setSortBy(sortBy != null ? sortBy : "") .build(); - return ResponseEntity.ok().body(collectorService.listCollectorHostnames(request)); + return ResponseEntity.ok().body(collectorOpsService.listCollectorHostnames(request)); } catch (BadRequestAlertException e) { String msg = ctx + ": " + e.getLocalizedMessage(); log.error(msg); @@ -196,8 +167,8 @@ public ResponseEntity listCollectorByHostNameAndModul @RequestParam CollectorModuleEnum module) { final String ctx = CLASSNAME + ".listCollectorByHostNameAndModule"; try { - return ResponseEntity.ok().body(collectorService.listCollector( - collectorService.getListRequestByHostnameAndModule(hostname, module))); + return ResponseEntity.ok().body(collectorOpsService.listCollector( + collectorOpsService.getListRequestByHostnameAndModule(hostname, module))); } catch (BadRequestAlertException e) { String msg = ctx + ": " + e.getLocalizedMessage(); log.error(msg); @@ -224,7 +195,7 @@ public ResponseEntity> getModuleGroups(@PathVariable String public ResponseEntity updateGroup(@Valid @RequestBody UtmNetworkScanResource.UpdateGroupRequestBody body) { final String ctx = CLASSNAME + ".updateGroup"; try { - collectorService.updateGroup(body.getAssetsIds(), body.getAssetGroupId()); + collectorOpsService.updateGroup(body.getAssetsIds(), body.getAssetGroupId()); return ResponseEntity.ok().build(); } catch (Exception e) { String msg = ctx + ": " + e.getMessage(); @@ -240,7 +211,7 @@ public ResponseEntity> searchGroupsByFilter(AssetGroupFilter final String ctx = CLASSNAME + ".searchGroupsByFilter"; try { - Page page = collectorService.searchGroupsByFilter(filter, pageable); + Page page = collectorOpsService.searchGroupsByFilter(filter, pageable); HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/utm-asset-groups/searchGroupsByFilter"); return ResponseEntity.ok().headers(headers).body(page.getContent()); } catch (Exception e) { @@ -257,7 +228,7 @@ public ResponseEntity> searchByFilters(@ParameterObject Netwo @ParameterObject Pageable pageable) { final String ctx = CLASSNAME + ".searchByFilters"; try { - collectorService.listCollector(ListRequest.newBuilder() + collectorOpsService.listCollector(ListRequest.newBuilder() .setPageNumber(0) .setPageSize(1000000) .setSortBy("") @@ -279,7 +250,7 @@ public ResponseEntity deleteCollector(@PathVariable Long id) { try { log.debug("REST request to delete UtmCollector : {}", id); - collectorService.deleteCollector(id); + collectorOpsService.deleteCollector(id); return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert("UtmCollector", id.toString())).build(); } catch (Exception e) { applicationEventService.createEvent(e.getMessage(), ApplicationEventType.ERROR); @@ -289,17 +260,17 @@ public ResponseEntity deleteCollector(@PathVariable Long id) { } @PostMapping("/collectors-config") - public ResponseEntity> upsertCollectorsConfig(@RequestBody List collectors) { + public ResponseEntity> upsertCollectorsConfig(@RequestBody List collectors) { Map results = new HashMap<>(); final String ctx = CLASSNAME + ".upsertCollectorsConfig"; CollectorConfig cacheConfig = null; List> collectorsResults = new ArrayList<>(); - for (CollectorConfigKeysDTO collectorConfig : collectors) { + for (CollectorConfigDTO collectorConfig : collectors) { Map collectorResult = new HashMap<>(); collectorResult.put("collectorId", collectorConfig.getCollector().getId()); try { - cacheConfig = this.collectorService.cacheCurrentCollectorConfig(collectorConfig.getCollector()); + cacheConfig = this.collectorOpsService.cacheCurrentCollectorConfig(collectorConfig.getCollector()); this.upsert(collectorConfig); collectorResult.put("status", "success"); } catch (Exception e) { @@ -355,12 +326,12 @@ private ResponseEntity logAndResponse(ErrorResponse error) { return ResponseUtil.buildErrorResponse(error.getStatus(), error.getMessage()); } - private void upsert(CollectorConfigKeysDTO collectorConfig) throws Exception { + private void upsert(CollectorConfigDTO collectorConfig) throws Exception { // Update local database with new configuration - this.collectorService.updateCollectorConfigurationKeys(collectorConfig); + this.collectorOpsService.updateCollectorConfigurationKeys(collectorConfig); // Attempt to update collector configuration via gRPC - this.collectorService.updateCollectorConfigViaGrpc(collectorConfig, collectorConfig.getCollector()); + this.collectorOpsService.updateCollectorConfigViaGrpc(collectorConfig, collectorConfig.getCollector()); } } From edb93e769e9110f2c41f421525d421c10f6e5272 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:49 -0600 Subject: [PATCH 028/240] feat: add logging to updateConfigurationKeys method in UtmModuleGroupConfigurationService --- .../UtmModuleGroupConfigurationService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java index 1311b5f4d..be415d1fb 100644 --- a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java +++ b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java @@ -10,6 +10,7 @@ import com.park.utmstack.util.CipherUtil; import com.park.utmstack.util.exceptions.ApiException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,6 +29,7 @@ @Service @Transactional @RequiredArgsConstructor +@Slf4j public class UtmModuleGroupConfigurationService { private static final String CLASSNAME = "UtmModuleGroupConfigurationService"; @@ -54,7 +56,7 @@ public void createConfigurationKeys(List keys) thro * @param keys List of configuration keys to save * @throws Exception In case of any error */ - public UtmModule updateConfigurationKeys(Long moduleId, List keys) throws Exception { + public UtmModule updateConfigurationKeys(Long moduleId, List keys) { final String ctx = CLASSNAME + ".updateConfigurationKeys"; try { if (CollectionUtils.isEmpty(keys)) @@ -77,7 +79,8 @@ public UtmModule updateConfigurationKeys(Long moduleId, List new ApiException(String.format("Module with ID %1$s not found", moduleId), HttpStatus.NOT_FOUND)); } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); + log.error("{}: Error updating configuration keys: {}", ctx, e.getMessage()); + throw new ApiException(String.format("%s: Error updating configuration keys: %s", ctx, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } } From f77647665545f675b226f635388743ee58849f79 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 15:22:03 -0600 Subject: [PATCH 029/240] fix(module.service): return full response body instead of filtering AS_400 module Signed-off-by: Manuel Abascal --- frontend/src/app/app-module/services/module.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/app-module/services/module.service.ts b/frontend/src/app/app-module/services/module.service.ts index e6e8da2c3..11f4fe494 100644 --- a/frontend/src/app/app-module/services/module.service.ts +++ b/frontend/src/app/app-module/services/module.service.ts @@ -83,7 +83,7 @@ export class ModuleService { m.prettyName = m.prettyName + ' GravityZone'; } }); - return response.body.filter(m => m.moduleName !== this.utmModulesEnum.AS_400); + return response.body; }), catchError(error => { console.error(error); From 9969fcea20d39e49d36d2dbaa52dbf70aca5f9ae Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 30 Jan 2026 12:45:46 -0600 Subject: [PATCH 030/240] refactor(collector): simplify DTOs and enhance service methods for listing collectors --- .../service/collectors/CollectorService.java | 97 ++++++++++++++++++- .../dto/collectors/dto/CollectorDTO.java | 92 +----------------- .../dto/ListCollectorsResponseDTO.java | 20 +--- .../rest/collectors/UtmCollectorResource.java | 77 +++++---------- 4 files changed, 128 insertions(+), 158 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java index bbffd535a..beecb7759 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java @@ -1,28 +1,119 @@ package com.park.utmstack.service.collectors; import agent.CollectorOuterClass; -import com.park.utmstack.service.application_events.ApplicationEventService; +import com.park.utmstack.config.Constants; +import com.park.utmstack.domain.collector.UtmCollector; import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; +import com.park.utmstack.service.dto.collectors.CollectorHostnames; +import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; +import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; +import com.park.utmstack.service.grpc.ListRequest; +import com.park.utmstack.util.exceptions.ApiException; +import com.park.utmstack.web.rest.errors.BadRequestAlertException; +import com.utmstack.grpc.exception.CollectorServiceGrpcException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.stream.Collectors; + +import static com.park.utmstack.config.RestTemplateConfiguration.CLASSNAME; @Service +@Slf4j @RequiredArgsConstructor public class CollectorService { private final CollectorGrpcService collectorGrpcService; - private final ApplicationEventService eventService; private final UtmModuleGroupConfigurationService moduleGroupConfigurationService; private final CollectorConfigBuilder CollectorConfigBuilder; + private final UtmCollectorService utmCollectorService; public void upsertCollectorConfig(CollectorConfigDTO collectorConfig) { - this.moduleGroupConfigurationService.updateConfigurationKeys(collectorConfig.getModuleId(), collectorConfig.getKeys()); + this.moduleGroupConfigurationService.updateConfigurationKeys(collectorConfig.getModuleId(), collectorConfig.getKeys()); CollectorOuterClass.CollectorConfig collector = CollectorConfigBuilder.build(collectorConfig); collectorGrpcService.upsertCollectorConfig(collector); } + public ListCollectorsResponseDTO listCollector(ListRequest request) { + return this.getListCollector(request); + } + + public ListCollectorsResponseDTO listCollector(String hostname, CollectorModuleEnum module) { + + String query = ""; + if (module != null && StringUtils.hasText(hostname)) { + query = "module.Is=" + module.name() + "&hostname.Is=" + hostname; + } else if (StringUtils.hasText(hostname)) { + query = "hostname.Is=" + hostname; + } else if (module != null) { + query = "module.Is=" + module.name(); + } + + var request = ListRequest.newBuilder() + .setPageNumber(1) + .setPageSize(1000000) + .setSearchQuery(query) + .setSortBy("id,desc") + .build(); + + CollectorOuterClass.ListCollectorResponse collectorResponse = collectorGrpcService.listCollectors(request); + return mapToListCollectorsResponseDTO(collectorResponse); + } + + public CollectorHostnames listCollectorHostnames(ListRequest request) { + + + CollectorOuterClass.ListCollectorResponse response = collectorGrpcService.listCollectors(request); + CollectorHostnames collectorHostnames = new CollectorHostnames(); + + response.getRowsList().forEach(c -> { + collectorHostnames.getHostname().add(c.getHostname()); + }); + + return collectorHostnames; + + } + + private ListCollectorsResponseDTO getListCollector(ListRequest request) { + + CollectorOuterClass.ListCollectorResponse collectorResponse = collectorGrpcService.listCollectors(request); + return mapToListCollectorsResponseDTO(collectorResponse); + } + + + private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(CollectorOuterClass.ListCollectorResponse response) { + final String ctx = CLASSNAME + ".mapToListCollectorsResponseDTO"; + try { + ListCollectorsResponseDTO dto = new ListCollectorsResponseDTO(); + + List collectorDTOS = response.getRowsList().stream() + .map(this::protoToCollectorDto) + .collect(Collectors.toList()); + + this.utmCollectorService.synchronize(collectorDTOS); + + dto.setCollectors(collectorDTOS); + dto.setTotal(response.getTotal()); + + return dto; + } catch (Exception e) { + log.error("{}: Error mapping ListCollectorResponse to ListCollectorsResponseDTO: {}", ctx, e.getMessage()); + throw new ApiException(String.format("%s: Error mapping ListCollectorResponse to ListCollectorsResponseDTO: %s", ctx, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + private CollectorDTO protoToCollectorDto(CollectorOuterClass.Collector collector) { + UtmCollector utmCollector = this.utmCollectorService.saveCollector(collector); + return new CollectorDTO(utmCollector); + } + } diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorDTO.java index 703656752..3dbb43e3f 100644 --- a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorDTO.java +++ b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorDTO.java @@ -1,12 +1,15 @@ package com.park.utmstack.service.dto.collectors.dto; -import agent.CollectorOuterClass.Collector; import com.park.utmstack.domain.collector.UtmCollector; import com.park.utmstack.domain.network_scan.UtmAssetGroup; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; import com.park.utmstack.service.dto.collectors.CollectorStatusEnum; +import lombok.Getter; +import lombok.Setter; +@Setter +@Getter public class CollectorDTO { private int id; private CollectorStatusEnum status; @@ -39,91 +42,4 @@ public CollectorDTO(UtmCollector collector) { this.active = collector.isActive(); } - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public CollectorStatusEnum getStatus() { - return status; - } - - public void setStatus(CollectorStatusEnum status) { - this.status = status; - } - - public String getCollectorKey() { - return collectorKey; - } - - public void setCollectorKey(String collectorKey) { - this.collectorKey = collectorKey; - } - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public String getHostname() { - return hostname; - } - - public void setHostname(String hostname) { - this.hostname = hostname; - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public CollectorModuleEnum getModule() { - return module; - } - - public void setModule(CollectorModuleEnum module) { - this.module = module; - } - - public String getLastSeen() { - return lastSeen; - } - - public void setLastSeen(String lastSeen) { - this.lastSeen = lastSeen; - } - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public UtmAssetGroup getGroup() { - return group; - } - - public void setGroup(UtmAssetGroup group) { - this.group = group; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } } diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/ListCollectorsResponseDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/ListCollectorsResponseDTO.java index 4130476a7..d38813f75 100644 --- a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/ListCollectorsResponseDTO.java +++ b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/ListCollectorsResponseDTO.java @@ -1,26 +1,14 @@ package com.park.utmstack.service.dto.collectors.dto; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; +import lombok.Getter; +import lombok.Setter; import java.util.List; +@Setter +@Getter public class ListCollectorsResponseDTO { private List collectors; private int total; - - public List getCollectors() { - return collectors; - } - - public void setCollectors(List collectors) { - this.collectors = collectors; - } - - public int getTotal() { - return total; - } - - public void setTotal(int total) { - this.total = total; - } } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index f6dfc8303..43cd5891f 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -96,25 +96,18 @@ public ResponseEntity listCollectorsByModule(@Request @RequestParam(required = false) Integer pageSize, @RequestParam(required = false) CollectorModuleEnum module, @RequestParam(required = false) String sortBy) { - final String ctx = CLASSNAME + ".listCollectorsByModule"; - try { - ListRequest request = ListRequest.newBuilder() - .setPageNumber(pageNumber != null ? pageNumber : 0) - .setPageSize(pageSize != null ? pageSize : 1000000) - .setSearchQuery(module != null ? "module.Is=" + module.name() : "") - .setSortBy(sortBy != null ? sortBy : "") - .build(); - - ListCollectorsResponseDTO response = collectorOpsService.listCollector(request); - HttpHeaders headers = new HttpHeaders(); - headers.add("X-Total-Count", Long.toString(response.getTotal())); - return ResponseEntity.ok().headers(headers).body(response); - } catch (BadRequestAlertException e) { - String msg = ctx + ": " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } + + ListRequest request = ListRequest.newBuilder() + .setPageNumber(pageNumber != null ? pageNumber : 0) + .setPageSize(pageSize != null ? pageSize : 1000000) + .setSearchQuery(module != null ? "module.Is=" + module.name() : "") + .setSortBy(sortBy != null ? sortBy : "") + .build(); + + ListCollectorsResponseDTO response = collectorService.listCollector(request); + HttpHeaders headers = new HttpHeaders(); + headers.add("X-Total-Count", Long.toString(response.getTotal())); + return ResponseEntity.ok().headers(headers).body(response); } /** @@ -132,26 +125,16 @@ public ResponseEntity listCollectorHostNames(@RequestParam(r @RequestParam(required = false) Integer pageSize, @RequestParam(required = false) CollectorModuleEnum module, @RequestParam(required = false) String sortBy) { - final String ctx = CLASSNAME + ".listCollectorHostNames"; - try { - ListRequest request = ListRequest.newBuilder() - .setPageNumber(pageNumber != null ? pageNumber : 0) - .setPageSize(pageSize != null ? pageSize : 1000000) - .setSearchQuery(module != null ? "module.Is=" + module : "") - .setSortBy(sortBy != null ? sortBy : "") - .build(); - return ResponseEntity.ok().body(collectorOpsService.listCollectorHostnames(request)); - } catch (BadRequestAlertException e) { - String msg = ctx + ": " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } catch (CollectorServiceGrpcException e) { - String msg = ctx + ": UtmCollector manager is not available or the parameters are wrong, please check." + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_GATEWAY, msg); - } + + ListRequest request = ListRequest.newBuilder() + .setPageNumber(pageNumber != null ? pageNumber : 0) + .setPageSize(pageSize != null ? pageSize : 1000000) + .setSearchQuery(module != null ? "module.Is=" + module : "") + .setSortBy(sortBy != null ? sortBy : "") + .build(); + + return ResponseEntity.ok().body(collectorService.listCollectorHostnames(request)); + } /** @@ -165,16 +148,8 @@ public ResponseEntity listCollectorHostNames(@RequestParam(r @GetMapping("/collector-by-hostname-and-module") public ResponseEntity listCollectorByHostNameAndModule(@RequestParam String hostname, @RequestParam CollectorModuleEnum module) { - final String ctx = CLASSNAME + ".listCollectorByHostNameAndModule"; - try { - return ResponseEntity.ok().body(collectorOpsService.listCollector( - collectorOpsService.getListRequestByHostnameAndModule(hostname, module))); - } catch (BadRequestAlertException e) { - String msg = ctx + ": " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } + + return ResponseEntity.ok().body(collectorService.listCollector(hostname, module)); } @GetMapping("/groups-by-collectors/{collectorId}") @@ -191,7 +166,7 @@ public ResponseEntity> getModuleGroups(@PathVariable String } } - @PutMapping("/updateGroup") + /*@PutMapping("/updateGroup") public ResponseEntity updateGroup(@Valid @RequestBody UtmNetworkScanResource.UpdateGroupRequestBody body) { final String ctx = CLASSNAME + ".updateGroup"; try { @@ -204,7 +179,7 @@ public ResponseEntity updateGroup(@Valid @RequestBody UtmNetworkScanResour return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( HeaderUtil.createFailureAlert("", "", msg)).body(null); } - } + }*/ @GetMapping("/searchGroupsByFilter") public ResponseEntity> searchGroupsByFilter(AssetGroupFilter filter, Pageable pageable) { From 29426c95ab922d7e7766cd613ad835b77ed9cdf9 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 11 Feb 2026 13:13:18 -0600 Subject: [PATCH 031/240] feat(assets-view): refactor asset detail handling and improve status display Signed-off-by: Manuel Abascal --- .../assets-view/assets-view.component.html | 9 +++---- .../assets-view/assets-view.component.ts | 24 +++++++++++-------- .../shared/types/net-scan.type.ts | 2 ++ .../utm-agent-connect.component.ts | 3 ++- .../utm-agent-detail.component.ts | 2 ++ 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/assets-discover/assets-view/assets-view.component.html b/frontend/src/app/assets-discover/assets-view/assets-view.component.html index 2823407bb..aae1ae3bf 100644 --- a/frontend/src/app/assets-discover/assets-view/assets-view.component.html +++ b/frontend/src/app/assets-discover/assets-view/assets-view.component.html @@ -264,13 +264,14 @@
-
+
- Agent {{ agent }} detail + {{ assetSelected.isAgent }} + {{ assetSelected.isAgent ? 'Agent' : 'Source' }} {{ assetName }} details
diff --git a/frontend/src/app/assets-discover/assets-view/assets-view.component.ts b/frontend/src/app/assets-discover/assets-view/assets-view.component.ts index e5cd403d0..d85f9c3ee 100644 --- a/frontend/src/app/assets-discover/assets-view/assets-view.component.ts +++ b/frontend/src/app/assets-discover/assets-view/assets-view.component.ts @@ -55,7 +55,7 @@ export class AssetsViewComponent implements OnInit, OnDestroy { page = 0; loading = false; itemsPerPage = ITEMS_PER_PAGE; - viewAssetDetail: NetScanType; + assetSelected: NetScanType; sortBy = AssetFieldEnum.ASSET_IS_ALIVE + ',DESC'; assetsFields: UtmFieldType[] = ASSETS_FIELDS; checkbox: any; @@ -83,7 +83,7 @@ export class AssetsViewComponent implements OnInit, OnDestroy { deleting: string[] = []; agentConsole: NetScanType; reasonRun: IncidentCommandType; - agent: string; + assetName: string; noData = false; destroy$ = new Subject(); @@ -280,7 +280,7 @@ export class AssetsViewComponent implements OnInit, OnDestroy { case AssetFieldEnum.ASSET_METRICS: break; default: - this.viewAssetDetail = asset; + this.assetSelected = asset; } } @@ -391,8 +391,12 @@ export class AssetsViewComponent implements OnInit, OnDestroy { getLastInput(asset: NetScanType) { if (asset.dataInputList.length > 0) { - const lastInput = asset.dataInputList[asset.dataInputList.length - 1].timestamp; + + const lastInputStatus = asset.dataInputList[asset.dataInputList.length - 1]; + const lastInput = lastInputStatus.timestamp; + asset.lastInputTimestamp = lastInput; + asset.status = lastInputStatus.down; return this.formatTimestampToDate(lastInput); } @@ -406,17 +410,17 @@ export class AssetsViewComponent implements OnInit, OnDestroy { } toggleAsset(asset: NetScanType) { - if (this.viewAssetDetail && this.viewAssetDetail.id === asset.id) { - this.viewAssetDetail = undefined; + if (this.assetSelected && this.assetSelected.id === asset.id) { + this.assetSelected = undefined; } else { - this.viewAssetDetail = asset; + this.assetSelected = asset; } } viwAgentDetail(event: Event, asset: NetScanType) { event.stopPropagation(); - this.viewAssetDetail = asset; - this.agent = asset.assetName; + this.assetSelected = asset; + this.assetName = asset.assetName && asset.assetName !== '' ? asset.assetName : asset.assetIp; } isSourceConnected(asset: NetScanType, source: UtmDataInputStatus): boolean { @@ -439,7 +443,7 @@ export class AssetsViewComponent implements OnInit, OnDestroy { } closeDetail() { - this.agent = undefined; + this.assetName = undefined; this.reasonRun.reason = ''; } diff --git a/frontend/src/app/assets-discover/shared/types/net-scan.type.ts b/frontend/src/app/assets-discover/shared/types/net-scan.type.ts index 42212b4e4..2a30d2588 100644 --- a/frontend/src/app/assets-discover/shared/types/net-scan.type.ts +++ b/frontend/src/app/assets-discover/shared/types/net-scan.type.ts @@ -39,4 +39,6 @@ export class NetScanType { sortKey?: string; lastInput: string; lastInputTimestamp: number; + status?: boolean; + isAgent?: boolean; } diff --git a/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts b/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts index dfdc55190..ab4414ce9 100644 --- a/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts +++ b/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts @@ -23,6 +23,7 @@ export class UtmAgentConnectComponent implements OnInit, OnChanges { } ngOnInit() { + console.log('init'); this.hasNoReason = this.websocketCommand.reason === '' || !this.websocketCommand.reason; } @@ -47,7 +48,7 @@ export class UtmAgentConnectComponent implements OnInit, OnChanges { ip: this.asset.assetIp, hostname: this.asset.assetName, os: this.asset.assetOs, - status: AgentStatusEnum.OFFLINE, + status: !this.asset.status ? AgentStatusEnum.ONLINE : AgentStatusEnum.OFFLINE, platform: this.asset.assetOsPlatform, version: this.asset.assetOsMinorVersion, agentKey: '', diff --git a/frontend/src/app/shared/components/utm/util/utm-agent-detail/utm-agent-detail.component.ts b/frontend/src/app/shared/components/utm/util/utm-agent-detail/utm-agent-detail.component.ts index 7e229177b..5dd9b8c9d 100644 --- a/frontend/src/app/shared/components/utm/util/utm-agent-detail/utm-agent-detail.component.ts +++ b/frontend/src/app/shared/components/utm/util/utm-agent-detail/utm-agent-detail.component.ts @@ -28,6 +28,8 @@ export class UtmAgentDetailComponent implements OnInit { this.agentIp = this.agent.ip; this.ips = this.agent.addresses !== '' ? this.agent.addresses.split(',') : []; this.macs = this.agent.mac !== '' ? this.agent.mac.split(',') : []; + + console.log(this.agent); } public getAgentIcon(): string { From 9eecd501e07aced9a9d5651b4b0d25d49c9e33f9 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 11 Feb 2026 16:41:38 -0600 Subject: [PATCH 032/240] feat(RequestDsl): enhance search request handling for LIST_CHART visualization --- .../requests/RequestDsl.java | 79 +++++++++++++++---- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java b/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java index cb49b9df1..a5c35aae6 100644 --- a/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java +++ b/backend/src/main/java/com/park/utmstack/util/chart_builder/elasticsearch_dsl/requests/RequestDsl.java @@ -18,10 +18,7 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public class RequestDsl { @@ -74,7 +71,25 @@ private void applyPagination(Pageable pageable, int top) { if (pageable != null && pageable.isPaged()) { SearchUtil.applyPaginationAndSort(searchRequestBuilder, pageable, top); } else { - searchRequestBuilder.size(10000); + if (visualization.getChartType() == ChartType.LIST_CHART) { + + searchRequestBuilder.size(10000); + + Set fields = new HashSet<>(); + AggregationType agg = visualization.getAggregationType(); + if (agg != null && agg.getBucket() != null) { + collectBucketFields(agg.getBucket(), fields); + } + + List includeList = new ArrayList<>(fields) + .stream() + .map(this::normalizeField) + .toList(); + + searchRequestBuilder.source(s -> s.filter(f -> f.excludes("*"))); + searchRequestBuilder.source(s -> s.filter(f + -> f.includes(includeList))); + } } } @@ -98,7 +113,6 @@ public SearchRequest.Builder getSearchSourceBuilderForCount() throws UtmElastics } - /** * Build an aggregation section for an elasticsearch dsl request * @@ -157,19 +171,19 @@ private Map buildBucketAggregation switch (bucket.getAggregation()) { case TERMS: TermsAggregation term = new TermsAggregation.Builder().field(bucket.getField()) - .size(bucket.getTerms().getSize()).order(List.of(Map.of(bucket.getTerms().getSortBy(), - bucket.getTerms().getAsc() ? SortOrder.Asc : SortOrder.Desc))).build(); + .size(bucket.getTerms().getSize()).order(List.of(Map.of(bucket.getTerms().getSortBy(), + bucket.getTerms().getAsc() ? SortOrder.Asc : SortOrder.Desc))).build(); bucketAggregations.put(bucket.toString(), new Aggregation.Builder().terms(term)); break; case RANGE: RangeAggregation range = new RangeAggregation.Builder().field(bucket.getField()) - .ranges(bucket.getRanges().stream().map(r -> AggregationRange.of(a -> a.from(String.valueOf(r.getFrom())) - .to(String.valueOf(r.getTo())))).collect(Collectors.toList())).build(); + .ranges(bucket.getRanges().stream().map(r -> AggregationRange.of(a -> a.from(String.valueOf(r.getFrom())) + .to(String.valueOf(r.getTo())))).collect(Collectors.toList())).build(); bucketAggregations.put(bucket.toString(), new Aggregation.Builder().range(range)); break; case DATE_HISTOGRAM: DateHistogramAggregation.Builder histogram = new DateHistogramAggregation.Builder().field(bucket.getField()) - .timeZone(TimezoneUtil.getAppTimezone().toString()); + .timeZone(TimezoneUtil.getAppTimezone().toString()); String interval = bucket.getDateHistogram().getInterval(); if (bucket.getDateHistogram().isFixedInterval()) histogram.fixedInterval(i -> i.time(interval)); @@ -204,24 +218,24 @@ private Map buildMetricAggregation(List metrics) { switch (metric.getAggregation()) { case AVERAGE: metricAggregations.put(metric.getId(), Aggregation.of(agg -> - agg.avg(avg -> avg.field(metric.getField())))); + agg.avg(avg -> avg.field(metric.getField())))); break; case MAX: metricAggregations.put(metric.getId(), Aggregation.of(agg -> - agg.max(max -> max.field(metric.getField())))); + agg.max(max -> max.field(metric.getField())))); break; case MIN: metricAggregations.put(metric.getId(), Aggregation.of(agg -> - agg.min(min -> min.field(metric.getField())))); + agg.min(min -> min.field(metric.getField())))); break; case SUM: metricAggregations.put(metric.getId(), Aggregation.of(agg -> - agg.sum(sum -> sum.field(metric.getField())))); + agg.sum(sum -> sum.field(metric.getField())))); break; case MEDIAN: metricAggregations.put(metric.getId(), Aggregation.of(agg -> - agg.percentiles(percentile -> percentile.field(metric.getField()) - .keyed(false).percents(50D)))); + agg.percentiles(percentile -> percentile.field(metric.getField()) + .keyed(false).percents(50D)))); break; } } @@ -230,4 +244,35 @@ private Map buildMetricAggregation(List metrics) { throw new RuntimeException(ctx + ": " + e.getMessage()); } } + + private void collectBucketFields(Bucket bucket, Set fields) { + if (bucket == null) return; + + // 1. Field of this bucket (covers terms, ranges, date histogram) + if (bucket.getField() != null && !bucket.getField().isEmpty()) { + fields.add(bucket.getField()); + } + + // 2. Date histogram (field is in the bucket, not in DateHistogramBucket) + if (bucket.getDateHistogram() != null && + bucket.getField() != null && + !bucket.getField().isEmpty()) { + fields.add(bucket.getField()); + } + + // 3. Recurse into subBucket + if (bucket.getSubBucket() != null) { + collectBucketFields(bucket.getSubBucket(), fields); + } + } + + + private String normalizeField(String field) { + if (field.endsWith(".keyword")) { + return field.substring(0, field.length() - ".keyword".length()); + } + return field; + } + + } From f91ac27251084aa4a6ee703ab54050b27baa9b09 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 11 Feb 2026 16:41:51 -0600 Subject: [PATCH 033/240] feat(RequestDsl): enhance search request handling for LIST_CHART visualization --- .../park/utmstack/domain/chart_builder/UtmVisualization.java | 2 +- .../src/main/java/com/park/utmstack/util/UtilSerializer.java | 2 +- .../utmstack/util/exceptions/UtmSerializationException.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/domain/chart_builder/UtmVisualization.java b/backend/src/main/java/com/park/utmstack/domain/chart_builder/UtmVisualization.java index d3f0b0875..0f999ce11 100644 --- a/backend/src/main/java/com/park/utmstack/domain/chart_builder/UtmVisualization.java +++ b/backend/src/main/java/com/park/utmstack/domain/chart_builder/UtmVisualization.java @@ -219,7 +219,7 @@ public void setChartConfig(String chartConfig) { this.chartConfig = chartConfig; } - public AggregationType getAggregationType() throws UtmSerializationException { + public AggregationType getAggregationType() { if (StringUtils.hasText(aggregation)) aggregationType = UtilSerializer.jsonDeserialize(AggregationType.class, aggregation); return aggregationType; diff --git a/backend/src/main/java/com/park/utmstack/util/UtilSerializer.java b/backend/src/main/java/com/park/utmstack/util/UtilSerializer.java index 7eec18a6a..9784a321a 100644 --- a/backend/src/main/java/com/park/utmstack/util/UtilSerializer.java +++ b/backend/src/main/java/com/park/utmstack/util/UtilSerializer.java @@ -67,7 +67,7 @@ public static String jsonSerialize(T obj) throws UtmSerializationException { } } - public static T jsonDeserialize(Class type, String json) throws UtmSerializationException { + public static T jsonDeserialize(Class type, String json) { String ctx = CLASS_NAME + ".jsonDeserialize"; try { ObjectMapper om = new ObjectMapper(); diff --git a/backend/src/main/java/com/park/utmstack/util/exceptions/UtmSerializationException.java b/backend/src/main/java/com/park/utmstack/util/exceptions/UtmSerializationException.java index 7b0aed56b..d92ebd4dc 100644 --- a/backend/src/main/java/com/park/utmstack/util/exceptions/UtmSerializationException.java +++ b/backend/src/main/java/com/park/utmstack/util/exceptions/UtmSerializationException.java @@ -1,6 +1,6 @@ package com.park.utmstack.util.exceptions; -public class UtmSerializationException extends Exception { +public class UtmSerializationException extends RuntimeException { public UtmSerializationException(String message) { super(message); } From 69157b84e6b7fdf59b0503d19f5cb62b9d8695d9 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 11 Feb 2026 16:43:22 -0600 Subject: [PATCH 034/240] feat: add updates for Windows visualizations and default time range adjustments --- ...60211004_update_windows_visualizations.xml | 49 +++++++++++++++++++ ...005_update_default_time_visualizations.xml | 28 +++++++++++ .../resources/config/liquibase/master.xml | 4 ++ 3 files changed, 81 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260211005_update_default_time_visualizations.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml new file mode 100644 index 000000000..ca13dffdc --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260211005_update_default_time_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260211005_update_default_time_visualizations.xml new file mode 100644 index 000000000..33d13b25c --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260211005_update_default_time_visualizations.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index a1ba2df2f..fb0f51e27 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -411,6 +411,10 @@ + + + + From d2f2dea1ad970f7f5b5355bd3616a2dacbb693d4 Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Thu, 12 Feb 2026 16:03:35 +0300 Subject: [PATCH 035/240] feat(netflow-filter): add IANA protocol mapping and improve field processing --- filters/netflow/netflow.yml | 947 ++++++++++++++++++++++++++++++++++-- 1 file changed, 917 insertions(+), 30 deletions(-) diff --git a/filters/netflow/netflow.yml b/filters/netflow/netflow.yml index a92c36581..402618f72 100644 --- a/filters/netflow/netflow.yml +++ b/filters/netflow/netflow.yml @@ -1,4 +1,4 @@ -# Netflow firewall module filter, version 3.1.0 +# Netflow firewall module filter, version 3.1.1 # Based in docs and Netflow Generator (Solarwinds) for send log # # Documentations @@ -80,8 +80,8 @@ pipeline: - grok: patterns: - fieldName: log.irrelevant4 - pattern: '{{.data}}proto="\[' - - fieldName: protocol + pattern: '{{.data}}proto="/\[' + - fieldName: log.protocol pattern: '{{.data}}\]"' source: raw @@ -158,31 +158,8 @@ pipeline: from: - log.dest_ip to: target.ip - - rename: - from: - - log.srcPortg - to: origin.port - - rename: - from: - - log.src_port - to: origin.port - - rename: - from: - - log.dstPortg - to: target.port - - rename: - from: - - log.dest_port - to: target.port - - # Fields conversions - - cast: - to: 'int' - fields: - - origin.port - - target.port - # Removing unused caracters + # Remove unused caracters and estandarize fields - trim: function: prefix substring: '"' @@ -198,6 +175,7 @@ pipeline: - log.version - target.ip - origin.ip + - trim: function: suffix substring: '"' @@ -222,15 +200,62 @@ pipeline: - log.totalPackets - log.inEthernet - log.outEthernet - - protocol - - origin.port - - target.port + - log.protocol + - log.srcPortg + - log.src_port + - log.dstPortg + - log.dest_port - log.destMask - log.sourceMask - log.tcpFlgs - log.destAs - log.sourceAs + - trim: + function: prefix + substring: '0' + fields: + - log.srcPortg + - log.src_port + - log.dstPortg + - log.dest_port + + - trim: + function: substring + substring: " " + fields: + - log.srcPortg + - log.src_port + - log.dstPortg + - log.dest_port + + # Fields conversions + - cast: + to: 'int' + fields: + - log.srcPortg + - log.src_port + - log.dstPortg + - log.dest_port + + # Rename fields to fields base + - rename: + from: + - log.srcPortg + to: origin.port + - rename: + from: + - log.src_port + to: origin.port + - rename: + from: + - log.dstPortg + to: target.port + - rename: + from: + - log.dest_port + to: target.port + # Adding geolocation to origin.ip - dynamic: plugin: com.utmstack.geolocation @@ -246,6 +271,866 @@ pipeline: source: target.ip destination: target.geolocation where: exists("target.ip") + + # Adding protocol field based on IANA protocol numbers + - add: + function: "string" + params: + key: protocol + value: "HOPOPT" + where: equals("log.protocol", "0") + - add: + function: "string" + params: + key: protocol + value: "ICMP" + where: equals("log.protocol", "1") + - add: + function: "string" + params: + key: protocol + value: "IGMP" + where: equals("log.protocol", "2") + - add: + function: "string" + params: + key: protocol + value: "GGP" + where: equals("log.protocol", "3") + - add: + function: "string" + params: + key: protocol + value: "IP-in-IP" + where: equals("log.protocol", "4") + - add: + function: "string" + params: + key: protocol + value: "ST" + where: equals("log.protocol", "5") + - add: + function: "string" + params: + key: protocol + value: "TCP" + where: equals("log.protocol", "6") + - add: + function: "string" + params: + key: protocol + value: "CBT" + where: equals("log.protocol", "7") + - add: + function: "string" + params: + key: protocol + value: "EGP" + where: equals("log.protocol", "8") + - add: + function: "string" + params: + key: protocol + value: "IGP" + where: equals("log.protocol", "9") + - add: + function: "string" + params: + key: protocol + value: "BBN-RCC-MON" + where: equals("log.protocol", "10") + - add: + function: "string" + params: + key: protocol + value: "NVP-II" + where: equals("log.protocol", "11") + - add: + function: "string" + params: + key: protocol + value: "PUP" + where: equals("log.protocol", "12") + - add: + function: "string" + params: + key: protocol + value: "ARGUS" + where: equals("log.protocol", "13") + - add: + function: "string" + params: + key: protocol + value: "EMCON" + where: equals("log.protocol", "14") + - add: + function: "string" + params: + key: protocol + value: "XNET" + where: equals("log.protocol", "15") + - add: + function: "string" + params: + key: protocol + value: "CHAOS" + where: equals("log.protocol", "16") + - add: + function: "string" + params: + key: protocol + value: "UDP" + where: equals("log.protocol", "17") + - add: + function: "string" + params: + key: protocol + value: "MUX" + where: equals("log.protocol", "18") + - add: + function: "string" + params: + key: protocol + value: "DCN-MEAS" + where: equals("log.protocol", "19") + - add: + function: "string" + params: + key: protocol + value: "HMP" + where: equals("log.protocol", "20") + - add: + function: "string" + params: + key: protocol + value: "PRM" + where: equals("log.protocol", "21") + - add: + function: "string" + params: + key: protocol + value: "XNS-IDP" + where: equals("log.protocol", "22") + - add: + function: "string" + params: + key: protocol + value: "TRUNK-1" + where: equals("log.protocol", "23") + - add: + function: "string" + params: + key: protocol + value: "TRUNK-2" + where: equals("log.protocol", "24") + - add: + function: "string" + params: + key: protocol + value: "LEAF-1" + where: equals("log.protocol", "25") + - add: + function: "string" + params: + key: protocol + value: "LEAF-2" + where: equals("log.protocol", "26") + - add: + function: "string" + params: + key: protocol + value: "RDP" + where: equals("log.protocol", "27") + - add: + function: "string" + params: + key: protocol + value: "IRTP" + where: equals("log.protocol", "28") + - add: + function: "string" + params: + key: protocol + value: "ISO-TP4" + where: equals("log.protocol", "29") + - add: + function: "string" + params: + key: protocol + value: "NETBLT" + where: equals("log.protocol", "30") + - add: + function: "string" + params: + key: protocol + value: "MFE-NSP" + where: equals("log.protocol", "31") + - add: + function: "string" + params: + key: protocol + value: "MERIT-INP" + where: equals("log.protocol", "32") + - add: + function: "string" + params: + key: protocol + value: "DCCP" + where: equals("log.protocol", "33") + - add: + function: "string" + params: + key: protocol + value: "3PC" + where: equals("log.protocol", "34") + - add: + function: "string" + params: + key: protocol + value: "IDPR" + where: equals("log.protocol", "35") + - add: + function: "string" + params: + key: protocol + value: "XTP" + where: equals("log.protocol", "36") + - add: + function: "string" + params: + key: protocol + value: "DDP" + where: equals("log.protocol", "37") + - add: + function: "string" + params: + key: protocol + value: "IDPR-CMTP" + where: equals("log.protocol", "38") + - add: + function: "string" + params: + key: protocol + value: "TP++" + where: equals("log.protocol", "39") + - add: + function: "string" + params: + key: protocol + value: "IL" + where: equals("log.protocol", "40") + - add: + function: "string" + params: + key: protocol + value: "IPv6" + where: equals("log.protocol", "41") + - add: + function: "string" + params: + key: protocol + value: "SDRP" + where: equals("log.protocol", "42") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Route" + where: equals("log.protocol", "43") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Frag" + where: equals("log.protocol", "44") + - add: + function: "string" + params: + key: protocol + value: "IDRP" + where: equals("log.protocol", "45") + - add: + function: "string" + params: + key: protocol + value: "RSVP" + where: equals("log.protocol", "46") + - add: + function: "string" + params: + key: protocol + value: "GRE" + where: equals("log.protocol", "47") + - add: + function: "string" + params: + key: protocol + value: "DSR" + where: equals("log.protocol", "48") + - add: + function: "string" + params: + key: protocol + value: "BNA" + where: equals("log.protocol", "49") + - add: + function: "string" + params: + key: protocol + value: "ESP" + where: equals("log.protocol", "50") + - add: + function: "string" + params: + key: protocol + value: "AH" + where: equals("log.protocol", "51") + - add: + function: "string" + params: + key: protocol + value: "I-NLSP" + where: equals("log.protocol", "52") + - add: + function: "string" + params: + key: protocol + value: "SwIPe" + where: equals("log.protocol", "53") + - add: + function: "string" + params: + key: protocol + value: "NARP" + where: equals("log.protocol", "54") + - add: + function: "string" + params: + key: protocol + value: "MOBILE" + where: equals("log.protocol", "55") + - add: + function: "string" + params: + key: protocol + value: "TLSP" + where: equals("log.protocol", "56") + - add: + function: "string" + params: + key: protocol + value: "SKIP" + where: equals("log.protocol", "57") + - add: + function: "string" + params: + key: protocol + value: "IPv6-ICMP" + where: equals("log.protocol", "58") + - add: + function: "string" + params: + key: protocol + value: "IPv6-NoNxt" + where: equals("log.protocol", "59") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Opts" + where: equals("log.protocol", "60") + - add: + function: "string" + params: + key: protocol + value: "CFTP" + where: equals("log.protocol", "62") + - add: + function: "string" + params: + key: protocol + value: "SAT-EXPAK" + where: equals("log.protocol", "64") + - add: + function: "string" + params: + key: protocol + value: "KRYPTOLAN" + where: equals("log.protocol", "65") + - add: + function: "string" + params: + key: protocol + value: "RVD" + where: equals("log.protocol", "66") + - add: + function: "string" + params: + key: protocol + value: "IPPC" + where: equals("log.protocol", "67") + - add: + function: "string" + params: + key: protocol + value: "SAT-MON" + where: equals("log.protocol", "69") + - add: + function: "string" + params: + key: protocol + value: "VISA" + where: equals("log.protocol", "70") + - add: + function: "string" + params: + key: protocol + value: "IPCU" + where: equals("log.protocol", "71") + - add: + function: "string" + params: + key: protocol + value: "CPNX" + where: equals("log.protocol", "72") + - add: + function: "string" + params: + key: protocol + value: "CPHB" + where: equals("log.protocol", "73") + - add: + function: "string" + params: + key: protocol + value: "WSN" + where: equals("log.protocol", "74") + - add: + function: "string" + params: + key: protocol + value: "PVP" + where: equals("log.protocol", "75") + - add: + function: "string" + params: + key: protocol + value: "BR-SAT-MON" + where: equals("log.protocol", "76") + - add: + function: "string" + params: + key: protocol + value: "SUN-ND" + where: equals("log.protocol", "77") + - add: + function: "string" + params: + key: protocol + value: "WB-MON" + where: equals("log.protocol", "78") + - add: + function: "string" + params: + key: protocol + value: "WB-EXPAK" + where: equals("log.protocol", "79") + - add: + function: "string" + params: + key: protocol + value: "ISO-IP" + where: equals("log.protocol", "80") + - add: + function: "string" + params: + key: protocol + value: "VMTP" + where: equals("log.protocol", "81") + - add: + function: "string" + params: + key: protocol + value: "SECURE-VMTP" + where: equals("log.protocol", "82") + - add: + function: "string" + params: + key: protocol + value: "VINES" + where: equals("log.protocol", "83") + - add: + function: "string" + params: + key: protocol + value: "IPTM" + where: equals("log.protocol", "84") + - add: + function: "string" + params: + key: protocol + value: "NSFNET-IGP" + where: equals("log.protocol", "85") + - add: + function: "string" + params: + key: protocol + value: "DGP" + where: equals("log.protocol", "86") + - add: + function: "string" + params: + key: protocol + value: "TCF" + where: equals("log.protocol", "87") + - add: + function: "string" + params: + key: protocol + value: "EIGRP" + where: equals("log.protocol", "88") + - add: + function: "string" + params: + key: protocol + value: "OSPF" + where: equals("log.protocol", "89") + - add: + function: "string" + params: + key: protocol + value: "Sprite-RPC" + where: equals("log.protocol", "90") + - add: + function: "string" + params: + key: protocol + value: "LARP" + where: equals("log.protocol", "91") + - add: + function: "string" + params: + key: protocol + value: "MTP" + where: equals("log.protocol", "92") + - add: + function: "string" + params: + key: protocol + value: "AX.25" + where: equals("log.protocol", "93") + - add: + function: "string" + params: + key: protocol + value: "OS" + where: equals("log.protocol", "94") + - add: + function: "string" + params: + key: protocol + value: "MICP" + where: equals("log.protocol", "95") + - add: + function: "string" + params: + key: protocol + value: "SCC-SP" + where: equals("log.protocol", "96") + - add: + function: "string" + params: + key: protocol + value: "ETHERIP" + where: equals("log.protocol", "97") + - add: + function: "string" + params: + key: protocol + value: "ENCAP" + where: equals("log.protocol", "98") + - add: + function: "string" + params: + key: protocol + value: "GMTP" + where: equals("log.protocol", "100") + - add: + function: "string" + params: + key: protocol + value: "IFMP" + where: equals("log.protocol", "101") + - add: + function: "string" + params: + key: protocol + value: "PNNI" + where: equals("log.protocol", "102") + - add: + function: "string" + params: + key: protocol + value: "PIM" + where: equals("log.protocol", "103") + - add: + function: "string" + params: + key: protocol + value: "ARIS" + where: equals("log.protocol", "104") + - add: + function: "string" + params: + key: protocol + value: "SCPS" + where: equals("log.protocol", "105") + - add: + function: "string" + params: + key: protocol + value: "QNX" + where: equals("log.protocol", "106") + - add: + function: "string" + params: + key: protocol + value: "A/N" + where: equals("log.protocol", "107") + - add: + function: "string" + params: + key: protocol + value: "IPComp" + where: equals("log.protocol", "108") + - add: + function: "string" + params: + key: protocol + value: "SNP" + where: equals("log.protocol", "109") + - add: + function: "string" + params: + key: protocol + value: "Compaq-Peer" + where: equals("log.protocol", "110") + - add: + function: "string" + params: + key: protocol + value: "IPX-in-IP" + where: equals("log.protocol", "111") + - add: + function: "string" + params: + key: protocol + value: "VRRP" + where: equals("log.protocol", "112") + - add: + function: "string" + params: + key: protocol + value: "PGM" + where: equals("log.protocol", "113") + - add: + function: "string" + params: + key: protocol + value: "L2TP" + where: equals("log.protocol", "115") + - add: + function: "string" + params: + key: protocol + value: "DDX" + where: equals("log.protocol", "116") + - add: + function: "string" + params: + key: protocol + value: "IATP" + where: equals("log.protocol", "117") + - add: + function: "string" + params: + key: protocol + value: "STP" + where: equals("log.protocol", "118") + - add: + function: "string" + params: + key: protocol + value: "SRP" + where: equals("log.protocol", "119") + - add: + function: "string" + params: + key: protocol + value: "UTI" + where: equals("log.protocol", "120") + - add: + function: "string" + params: + key: protocol + value: "SMP" + where: equals("log.protocol", "121") + - add: + function: "string" + params: + key: protocol + value: "SM" + where: equals("log.protocol", "122") + - add: + function: "string" + params: + key: protocol + value: "PTP" + where: equals("log.protocol", "123") + - add: + function: "string" + params: + key: protocol + value: "IS-IS" + where: equals("log.protocol", "124") + - add: + function: "string" + params: + key: protocol + value: "FIRE" + where: equals("log.protocol", "125") + - add: + function: "string" + params: + key: protocol + value: "CRTP" + where: equals("log.protocol", "126") + - add: + function: "string" + params: + key: protocol + value: "CRUDP" + where: equals("log.protocol", "127") + - add: + function: "string" + params: + key: protocol + value: "SSCOPMCE" + where: equals("log.protocol", "128") + - add: + function: "string" + params: + key: protocol + value: "IPLT" + where: equals("log.protocol", "129") + - add: + function: "string" + params: + key: protocol + value: "SPS" + where: equals("log.protocol", "130") + - add: + function: "string" + params: + key: protocol + value: "PIPE" + where: equals("log.protocol", "131") + - add: + function: "string" + params: + key: protocol + value: "SCTP" + where: equals("log.protocol", "132") + - add: + function: "string" + params: + key: protocol + value: "FC" + where: equals("log.protocol", "133") + - add: + function: "string" + params: + key: protocol + value: "RSVP-E2E-IGNORE" + where: equals("log.protocol", "134") + - add: + function: "string" + params: + key: protocol + value: "Mobility-Header" + where: equals("log.protocol", "135") + - add: + function: "string" + params: + key: protocol + value: "UDPLite" + where: equals("log.protocol", "136") + - add: + function: "string" + params: + key: protocol + value: "MPLS-in-IP" + where: equals("log.protocol", "137") + - add: + function: "string" + params: + key: protocol + value: "manet" + where: equals("log.protocol", "138") + - add: + function: "string" + params: + key: protocol + value: "HIP" + where: equals("log.protocol", "139") + - add: + function: "string" + params: + key: protocol + value: "Shim6" + where: equals("log.protocol", "140") + - add: + function: "string" + params: + key: protocol + value: "WESP" + where: equals("log.protocol", "141") + - add: + function: "string" + params: + key: protocol + value: "ROHC" + where: equals("log.protocol", "142") + - add: + function: "string" + params: + key: protocol + value: "Ethernet" + where: equals("log.protocol", "143") + - add: + function: "string" + params: + key: protocol + value: "AGGFRAG" + where: equals("log.protocol", "144") + - add: + function: "string" + params: + key: protocol + value: "NSH" + where: equals("log.protocol", "145") + - add: + function: "string" + params: + key: protocol + value: "Homa" + where: equals("log.protocol", "146") + - add: + function: "string" + params: + key: protocol + value: "BIT-EMU" + where: equals("log.protocol", "147") # Removing unused fields - delete: @@ -261,6 +1146,8 @@ pipeline: - log.dstAs - log.srcAs - log.dstPort + - log.proto + - log.protocol - log.irrelevant - log.irrelevant1 - log.irrelevant2 From 2c8bd19361bd8da3d149f128206692d314477456 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 12 Feb 2026 07:48:11 -0600 Subject: [PATCH 036/240] feat(assets-view): refactor asset detail handling and improve status display Signed-off-by: Manuel Abascal --- .../assets-view/assets-view.component.html | 49 +++++++++++++++++-- .../assets-view/assets-view.component.scss | 4 ++ .../assets-view/assets-view.component.ts | 3 +- .../utm-agent-connect.component.ts | 1 - .../utm-agent-detail.component.ts | 2 - 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/assets-discover/assets-view/assets-view.component.html b/frontend/src/app/assets-discover/assets-view/assets-view.component.html index aae1ae3bf..c2fc7bdd1 100644 --- a/frontend/src/app/assets-discover/assets-view/assets-view.component.html +++ b/frontend/src/app/assets-discover/assets-view/assets-view.component.html @@ -270,8 +270,7 @@
- {{ assetSelected.isAgent }} - {{ assetSelected.isAgent ? 'Agent' : 'Source' }} {{ assetName }} details + {{ assetSelected.isAgent ? 'Agent' : 'Data Source' }} — {{ assetName }} Details
-
+
+ + +
+ Name:  + {{assetSelected.displayName}} +
+
+ MAC:  +
+ + {{card}} + +
+
+
+ Status:  + + + {{ !assetSelected.status ? 'ONLINE' : 'OFFLINE' }} + +
+
+ Type:  + + +
+
+ Group Source:  + + +
+
+ Comment:  + +
+
+ Last seen:  + {{ assetSelected.lastInput | date }} +
+
diff --git a/frontend/src/app/assets-discover/assets-view/assets-view.component.scss b/frontend/src/app/assets-discover/assets-view/assets-view.component.scss index e14e1b008..37338a59a 100644 --- a/frontend/src/app/assets-discover/assets-view/assets-view.component.scss +++ b/frontend/src/app/assets-discover/assets-view/assets-view.component.scss @@ -13,3 +13,7 @@ height: 100%; min-height: 0; } + +.min-w-70px { + min-width: 70px; +} diff --git a/frontend/src/app/assets-discover/assets-view/assets-view.component.ts b/frontend/src/app/assets-discover/assets-view/assets-view.component.ts index d85f9c3ee..b66991669 100644 --- a/frontend/src/app/assets-discover/assets-view/assets-view.component.ts +++ b/frontend/src/app/assets-discover/assets-view/assets-view.component.ts @@ -19,7 +19,6 @@ import {SortEvent} from '../../shared/directives/sortable/type/sort-event'; import {ChartValueSeparator} from '../../shared/enums/chart-value-separator'; import {ElasticOperatorsEnum} from '../../shared/enums/elastic-operators.enum'; import {IncidentOriginTypeEnum} from '../../shared/enums/incident-response/incident-origin-type.enum'; -import {UtmDatePipe} from '../../shared/pipes/date.pipe'; import {IncidentCommandType} from '../../shared/types/incident/incident-command.type'; import {UtmFieldType} from '../../shared/types/table/utm-field.type'; import {TimeFilterType} from '../../shared/types/time-filter.type'; @@ -44,7 +43,7 @@ import {SourceDataTypeConfigComponent} from '../source-data-type-config/source-d changeDetection: ChangeDetectionStrategy.OnPush }) export class AssetsViewComponent implements OnInit, OnDestroy { - assets$: Observable; + assets: NetScanType[]; // defaultTime: ElasticFilterDefaultTime = new ElasticFilterDefaultTime('now-30d', 'now'); pageWidth = window.innerWidth; diff --git a/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts b/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts index ab4414ce9..d6448b479 100644 --- a/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts +++ b/frontend/src/app/shared/components/utm/util/utm-agent-connect/utm-agent-connect.component.ts @@ -23,7 +23,6 @@ export class UtmAgentConnectComponent implements OnInit, OnChanges { } ngOnInit() { - console.log('init'); this.hasNoReason = this.websocketCommand.reason === '' || !this.websocketCommand.reason; } diff --git a/frontend/src/app/shared/components/utm/util/utm-agent-detail/utm-agent-detail.component.ts b/frontend/src/app/shared/components/utm/util/utm-agent-detail/utm-agent-detail.component.ts index 5dd9b8c9d..7e229177b 100644 --- a/frontend/src/app/shared/components/utm/util/utm-agent-detail/utm-agent-detail.component.ts +++ b/frontend/src/app/shared/components/utm/util/utm-agent-detail/utm-agent-detail.component.ts @@ -28,8 +28,6 @@ export class UtmAgentDetailComponent implements OnInit { this.agentIp = this.agent.ip; this.ips = this.agent.addresses !== '' ? this.agent.addresses.split(',') : []; this.macs = this.agent.mac !== '' ? this.agent.mac.split(',') : []; - - console.log(this.agent); } public getAgentIcon(): string { From 5945ee193bc4d1ce2bbaa95eeb1ccbc1556b248a Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 12 Feb 2026 08:59:44 -0600 Subject: [PATCH 037/240] feat(netflow-filter): add update for Netflow filter version 3.1.1 and enhance field processing --- .../20260212001_update_filter_netflow.xml | 1182 +++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 2 files changed, 1184 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260212001_update_filter_netflow.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260212001_update_filter_netflow.xml b/backend/src/main/resources/config/liquibase/changelog/20260212001_update_filter_netflow.xml new file mode 100644 index 000000000..4e0b88a0b --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260212001_update_filter_netflow.xml @@ -0,0 +1,1182 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index fb0f51e27..988412b6e 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -415,6 +415,8 @@ + + From 413ea27ecae99a891d42cf0e7daed72e638ac56f Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Thu, 12 Feb 2026 19:24:07 +0300 Subject: [PATCH 038/240] refactor(azure-plugin): extracts event processing logic into separate functions to handle JSON format detection (array vs object) --- plugins/azure/main.go | 146 ++++++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 61 deletions(-) diff --git a/plugins/azure/main.go b/plugins/azure/main.go index 1d53a0280..28712e7c6 100644 --- a/plugins/azure/main.go +++ b/plugins/azure/main.go @@ -419,67 +419,7 @@ func processPartition(ctx context.Context, pc *azeventhubs.ProcessorPartitionCli } for _, event := range events { - var logData map[string]any - if err := json.Unmarshal(event.Body, &logData); err != nil { - _ = catcher.Error("cannot parse event body", err, map[string]any{ - "group": groupName, - "partitionID": pc.PartitionID(), - "process": "plugin_com.utmstack.azure", - }) - continue - } - - if records, ok := logData["records"].([]any); ok && len(records) > 0 { - for _, record := range records { - recordMap, ok := record.(map[string]any) - if !ok { - _ = catcher.Error("invalid record format in records array", nil, map[string]any{ - "group": groupName, - "partitionID": pc.PartitionID(), - "process": "plugin_com.utmstack.azure", - }) - continue - } - - jsonLog, err := json.Marshal(recordMap) - if err != nil { - _ = catcher.Error("cannot encode record to JSON", err, map[string]any{ - "group": groupName, - "partitionID": pc.PartitionID(), - "process": "plugin_com.utmstack.azure", - }) - continue - } - - _ = plugins.EnqueueLog(&plugins.Log{ - Id: uuid.New().String(), - TenantId: defaultTenant, - DataType: "azure", - DataSource: groupName, - Timestamp: time.Now().UTC().Format(time.RFC3339Nano), - Raw: string(jsonLog), - }, "com.utmstack.azure") - } - } else { - jsonLog, err := json.Marshal(logData) - if err != nil { - _ = catcher.Error("cannot encode log to JSON", err, map[string]any{ - "group": groupName, - "partitionID": pc.PartitionID(), - "process": "plugin_com.utmstack.azure", - }) - continue - } - - _ = plugins.EnqueueLog(&plugins.Log{ - Id: uuid.New().String(), - TenantId: defaultTenant, - DataType: "azure", - DataSource: groupName, - Timestamp: time.Now().UTC().Format(time.RFC3339Nano), - Raw: string(jsonLog), - }, "com.utmstack.azure") - } + processEvent(event.Body, groupName) } if err := pc.UpdateCheckpoint(context.Background(), events[len(events)-1], nil); err != nil { @@ -518,6 +458,90 @@ func getAzureProcessor(group *config.ModuleGroup) AzureConfig { return azurePro } +func processEvent(eventBody []byte, groupName string) { + var firstByte byte + for _, b := range eventBody { + if b != ' ' && b != '\t' && b != '\n' && b != '\r' { + firstByte = b + break + } + } + + switch firstByte { + case '[': + processArrayEvent(eventBody, groupName) + case '{': + processObjectEvent(eventBody, groupName) + default: + _ = catcher.Error("invalid JSON format: expected array or object", nil, map[string]any{ + "group": groupName, + "process": "plugin_com.utmstack.azure", + }) + } +} + +func processArrayEvent(eventBody []byte, groupName string) { + var records []map[string]any + if err := json.Unmarshal(eventBody, &records); err != nil { + _ = catcher.Error("cannot parse event body as array", err, map[string]any{ + "group": groupName, + "process": "plugin_com.utmstack.azure", + }) + return + } + + for _, record := range records { + enqueueRecord(record, groupName) + } +} + +func processObjectEvent(eventBody []byte, groupName string) { + var logData map[string]any + if err := json.Unmarshal(eventBody, &logData); err != nil { + _ = catcher.Error("cannot parse event body as object", err, map[string]any{ + "group": groupName, + "process": "plugin_com.utmstack.azure", + }) + return + } + + if records, ok := logData["records"].([]any); ok && len(records) > 0 { + for _, record := range records { + recordMap, ok := record.(map[string]any) + if !ok { + _ = catcher.Error("invalid record format in records array", nil, map[string]any{ + "group": groupName, + "process": "plugin_com.utmstack.azure", + }) + continue + } + enqueueRecord(recordMap, groupName) + } + } else { + enqueueRecord(logData, groupName) + } +} + +func enqueueRecord(record map[string]any, groupName string) { + jsonLog, err := json.Marshal(record) + if err != nil { + _ = catcher.Error("cannot encode record to JSON", err, map[string]any{ + "group": groupName, + "process": "plugin_com.utmstack.azure", + }) + return + } + + _ = plugins.EnqueueLog(&plugins.Log{ + Id: uuid.New().String(), + TenantId: defaultTenant, + DataType: "azure", + DataSource: groupName, + Timestamp: time.Now().UTC().Format(time.RFC3339Nano), + Raw: string(jsonLog), + }, "com.utmstack.azure") +} + func connectionChecker(url string) error { checkConn := func() error { if err := checkConnection(url); err != nil { From a1b91701f040122227bfde3a9042bd28123f4de9 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 12 Feb 2026 12:40:36 -0600 Subject: [PATCH 039/240] feat(windows-visualizations): update outdated fields in Windows visualizations and normalize field names --- ...60211004_update_windows_visualizations.xml | 341 +++++++++++++++++- ...60212002_update_windows_visualizations.xml | 126 +++++++ .../resources/config/liquibase/master.xml | 2 + 3 files changed, 457 insertions(+), 12 deletions(-) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260212002_update_windows_visualizations.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml index ca13dffdc..843ca606c 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml @@ -2,7 +2,8 @@ + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog + http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"> @@ -10,36 +11,352 @@ diff --git a/backend/src/main/resources/config/liquibase/changelog/20260212002_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260212002_update_windows_visualizations.xml new file mode 100644 index 000000000..551e89fa1 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260212002_update_windows_visualizations.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 988412b6e..0eae6b4ee 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -417,6 +417,8 @@ + + From 6c0c23c343402bceac6d2b8405cf8eb78cf08c71 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 12 Feb 2026 14:39:37 -0600 Subject: [PATCH 040/240] feat(windows-visualizations): update field names in Windows visualizations for consistency --- ...60212003_update_windows_visualizations.xml | 413 ++++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 2 files changed, 415 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260212003_update_windows_visualizations.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260212003_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260212003_update_windows_visualizations.xml new file mode 100644 index 000000000..3a31a149f --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260212003_update_windows_visualizations.xml @@ -0,0 +1,413 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 0eae6b4ee..fc8d65232 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -419,6 +419,8 @@ + + From 9e88c992b0fe0a4b06cd69972bb4b436d824f395 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 12 Feb 2026 15:01:24 -0600 Subject: [PATCH 041/240] feat(open-alerts): optimize open alerts handling and improve local storage updates Signed-off-by: Manuel Abascal --- .../shared/services/open-alerts.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/data-management/alert-management/shared/services/open-alerts.service.ts b/frontend/src/app/data-management/alert-management/shared/services/open-alerts.service.ts index 81afe4da0..880718277 100644 --- a/frontend/src/app/data-management/alert-management/shared/services/open-alerts.service.ts +++ b/frontend/src/app/data-management/alert-management/shared/services/open-alerts.service.ts @@ -32,10 +32,12 @@ export class OpenAlertsService implements OnDestroy { takeUntil(this.destroy$), switchMap(() => this.alertOpenStatusService.getOpenAlert()), tap((response) => { - this.openAlerts = response.body; - this.localStorage.store(OPEN_ALERTS_KEY, this.openAlerts); - if (this.openAlerts >= 0 && this.openAlerts !== this.openAlerts) { - this.openAlertsBehaviorSubject.next(this.openAlerts); + const newValue = response.body; + + if (newValue !== this.openAlerts) { + this.openAlerts = newValue; + this.openAlertsBehaviorSubject.next(newValue); + this.localStorage.store(OPEN_ALERTS_KEY, newValue); } }), catchError((err) => { From 03eee3bfb7c137292188c90b5d01d20e94d652e8 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 12 Feb 2026 15:02:54 -0600 Subject: [PATCH 042/240] feat(visualization-list): integrate UtmToastService for error handling in visualization fetching Signed-off-by: Manuel Abascal --- .../visualization-list/visualization-list.component.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.ts b/frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.ts index 05adf6938..67f760466 100644 --- a/frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.ts +++ b/frontend/src/app/graphic-builder/visualization/visualization-list/visualization-list.component.ts @@ -18,6 +18,7 @@ import {VisualizationService} from '../shared/services/visualization.service'; import {VisualizationCreateComponent} from '../visualization-create/visualization-create.component'; import {VisualizationDeleteComponent} from '../visualization-delete/visualization-delete.component'; import {VisualizationImportComponent} from '../visualization-import/visualization-import.component'; +import {UtmToastService} from "../../../shared/alert/utm-toast.service"; @Component({ selector: 'app-visualization-list', @@ -54,7 +55,8 @@ export class VisualizationListComponent implements OnInit { private visualizationService: VisualizationService, private spinner: NgxSpinnerService, private router: Router, - private activatedRoute: ActivatedRoute) { + private activatedRoute: ActivatedRoute, + private toastService: UtmToastService) { } ngOnInit() { @@ -118,7 +120,7 @@ export class VisualizationListComponent implements OnInit { getVisualizationList() { this.visualizationService.query(this.requestParams).subscribe( (res: HttpResponse) => this.onSuccess(res.body, res.headers), - (res: HttpResponse) => this.onError(res.body) + (res: HttpResponse) => this.onError(res) ); } @@ -200,7 +202,8 @@ export class VisualizationListComponent implements OnInit { } private onError(error) { - // this.alertService.error(error.error, error.message, null); + this.toastService.showError('Error', 'An error occurred while fetching visualizations'); + this.loading = false; } } From 841428fc098143c084ca844337387462029ffcd2 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 12 Feb 2026 15:49:01 -0600 Subject: [PATCH 043/240] feat(windows-visualizations): update field names in Windows visualizations for consistency --- ...60212004_update_windows_visualizations.xml | 228 ++++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 2 files changed, 230 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260212004_update_windows_visualizations.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260212004_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260212004_update_windows_visualizations.xml new file mode 100644 index 000000000..ded05c74a --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260212004_update_windows_visualizations.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index fc8d65232..254211c36 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -421,6 +421,8 @@ + + From 146e6a375a2f9f3eab2dfb23bd192bdfa713610d Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 13 Feb 2026 07:45:10 -0600 Subject: [PATCH 044/240] feat(windows-visualizations): update field names in Windows visualizations for consistency --- ...60211004_update_windows_visualizations.xml | 377 +++++++++--------- ...60212002_update_windows_visualizations.xml | 44 +- ...60212005_update_windows_visualizations.xml | 309 ++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 4 files changed, 532 insertions(+), 200 deletions(-) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260212005_update_windows_visualizations.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml index 843ca606c..7bd925e57 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20260211004_update_windows_visualizations.xml @@ -10,95 +10,101 @@ + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 254211c36..09b3b629f 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -423,6 +423,8 @@ + + From 2b8a6f3b3256943dfc70f6aea2ab93941035d582 Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Fri, 13 Feb 2026 10:00:53 -0500 Subject: [PATCH 045/240] feat(bitdefender-gz): add renaming for log.deviceIps, log.dvchost, and log.act fields --- filters/antivirus/bitdefender_gz.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/filters/antivirus/bitdefender_gz.yml b/filters/antivirus/bitdefender_gz.yml index e8605dc75..fa430da73 100644 --- a/filters/antivirus/bitdefender_gz.yml +++ b/filters/antivirus/bitdefender_gz.yml @@ -183,16 +183,31 @@ pipeline: - log.src to: origin.ip + - rename: + from: + - log.deviceIps + to: origin.ip + + - rename: + from: + - log.dvchost + to: target.host + - rename: from: - log.sproc - to: origin.path + to: target.path - rename: from: - log.filePath to: origin.path + - rename: + from: + - log.act + to: action + # Removing unnecessary characters - trim: function: prefix From cafc3a1df56994a4bd28c4a92c7d7ce1ef4ddd5e Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 13 Feb 2026 09:23:35 -0600 Subject: [PATCH 046/240] feat(bitdefender-visualizations): normalize field names in Bitdefender GZ visualizations --- ...001_update_bit_defender_visualizations.xml | 66 +++++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 2 files changed, 68 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260213001_update_bit_defender_visualizations.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260213001_update_bit_defender_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260213001_update_bit_defender_visualizations.xml new file mode 100644 index 000000000..89f6c8542 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260213001_update_bit_defender_visualizations.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 09b3b629f..ee3760120 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -425,6 +425,8 @@ + + From a2e52de2133f9bf8684553ac7ecdb7ec97783feb Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 13 Feb 2026 09:39:57 -0600 Subject: [PATCH 047/240] feat(vmware-visualizations): normalize field names in VMware visualizations --- ...260213002_update_vmware_visualizations.xml | 51 +++++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 2 files changed, 53 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260213002_update_vmware_visualizations.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260213002_update_vmware_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260213002_update_vmware_visualizations.xml new file mode 100644 index 000000000..2d6732334 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260213002_update_vmware_visualizations.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index ee3760120..44637eacc 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -427,6 +427,8 @@ + + From a2d52aae01acaec40fcfbdda9b79a2afe11711f6 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 13 Feb 2026 09:47:56 -0600 Subject: [PATCH 048/240] feat(bitdefender-filter): add Bitdefender GravityZone filter update with field renaming and cleanup --- ...20260213003_update_filter_bit_defender.xml | 301 ++++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 2 files changed, 303 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260213003_update_filter_bit_defender.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260213003_update_filter_bit_defender.xml b/backend/src/main/resources/config/liquibase/changelog/20260213003_update_filter_bit_defender.xml new file mode 100644 index 000000000..cfb7be90c --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260213003_update_filter_bit_defender.xml @@ -0,0 +1,301 @@ + + + + + + + ' + - fieldName: log.syslogVersion + pattern: '{{.integer}}' + - fieldName: log.syslogDeviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}\w{{.time}}\w' + - fieldName: log.syslogHostIP + pattern: '{{.ipv4}}|{{.ipv6}}|{{.word}}' + - fieldName: log.notDefined + pattern: '{{.integer}}' + - fieldName: log.0trash + pattern: '{{.word}}\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.syslogPriority + pattern: '\<{{.data}}\>' + - fieldName: log.syslogVersion + pattern: '{{.integer}}' + - fieldName: log.syslogDeviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}\w{{.time}}\w' + - fieldName: log.hostId + pattern: '{{.word}}' + - fieldName: log.0trash + pattern: '{{.word}}' + - fieldName: log.processPid + pattern: '\[{{.integer}}\]' + - fieldName: log.1trash + pattern: '{{.word}}\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.syslogPriority + pattern: '\<{{.data}}\>' + - fieldName: log.syslogDeviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}\w{{.time}}\w' + - fieldName: log.hostId + pattern: '{{.word}}' + - fieldName: log.0trash + pattern: '{{.word}}' + - fieldName: log.processPid + pattern: '\[{{.integer}}\]' + - fieldName: log.1trash + pattern: '{{.word}}\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.syslogPriority + pattern: '\<{{.data}}\>' + - fieldName: log.syslogVersion + pattern: '{{.integer}}' + - fieldName: log.syslogDeviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}\w{{.time}}\w' + - fieldName: log.syslogHostIP + pattern: '{{.ipv4}}|{{.ipv6}}|{{.word}}' + - fieldName: log.0trash + pattern: '{{.word}}\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.syslogPriority + pattern: '\<{{.data}}\>' + - fieldName: log.syslogVersion + pattern: '{{.integer}}' + - fieldName: log.syslogDeviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}\w{{.time}}\w' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.syslogPriority + pattern: '\<{{.data}}\>' + - fieldName: log.0trash + pattern: '{{.word}}\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + - grok: + patterns: + - fieldName: log.cefVersion + pattern: 'CEF\:{{.integer}}' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: raw + + # Using grok to parse components of the cef_message + - grok: + patterns: + - fieldName: log.productVendor + pattern: '\|{{.data}}\|' + - fieldName: log.product + pattern: '{{.data}}\|' + - fieldName: log.productVersion + pattern: '{{.data}}\|' + - fieldName: log.signatureID + pattern: '{{.data}}\|' + - fieldName: log.eventType + pattern: '{{.data}}\|' + - fieldName: log.severity + pattern: '{{.data}}\|' + - fieldName: log.restData + pattern: '{{.greedy}}' + source: log.restData + + # Using grok to parse kv issued fields with space + - grok: + patterns: + - fieldName: log.2trash + pattern: '{{.data}}dvc=' + - fieldName: log.dvcToParse + pattern: '{{.data}}{{.word}}\=' + - fieldName: log.irrelevant + pattern: '{{.greedy}}' + source: log.restData + + - grok: + patterns: + - fieldName: log.2trash + pattern: '{{.data}}request=' + - fieldName: log.requestToParse + pattern: '{{.data}}{{.word}}\=' + - fieldName: log.irrelevant + pattern: '{{.greedy}}' + source: log.restData + + # Applying grok to remove unnecessary data + - grok: + patterns: + - fieldName: log.deviceIps + pattern: '{{.greedy}}{{.space}}' + - fieldName: log.irrelevant + pattern: '{{.greedy}}' + source: log.dvcToParse + + - grok: + patterns: + - fieldName: log.requested + pattern: '{{.greedy}}{{.space}}' + - fieldName: log.irrelevant + pattern: '{{.greedy}}' + source: log.requestToParse + + # Using the kv filter with default config, usefull in key-value logs + - kv: + fieldSplit: " " + valueSplit: "=" + source: log.restData + + # Renaming useful fields + - rename: + from: + - log.spt + to: origin.port + + - rename: + from: + - log.src + to: origin.ip + + - rename: + from: + - log.deviceIps + to: origin.ip + + - rename: + from: + - log.dvchost + to: target.host + + - rename: + from: + - log.sproc + to: target.path + + - rename: + from: + - log.filePath + to: origin.path + + - rename: + from: + - log.act + to: action + + # Removing unnecessary characters + - trim: + function: prefix + substring: '|' + fields: + - log.productVendor + + - trim: + function: suffix + substring: '|' + fields: + - log.productVendor + - log.product + - log.productVersion + - log.signatureID + - log.eventType + - log.severity + + - trim: + function: prefix + substring: '<' + fields: + - log.syslogPriority + + - trim: + function: suffix + substring: '>' + fields: + - log.syslogPriority + + - trim: + function: prefix + substring: '[' + fields: + - log.processPid + + - trim: + function: suffix + substring: ']' + fields: + - log.processPid + + # Adding geolocation to origin ip + - dynamic: + plugin: com.utmstack.geolocation + params: + source: origin.ip + destination: origin.geolocation + where: exists("origin.ip") + + # Reformat and field conversions + - cast: + fields: + - origin.port + to: int + + # Removing unused fields + - delete: + fields: + - log.0trash + - log.1trash + - log.2trash + - log.restData + - log.irrelevant + - log.spt + - log.src + - log.sproc + - log.filePath + - log.dvc + - log.request + - log.dvcToParse + - log.cefVersion$$ + WHERE id = 1514; + ]]> + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 44637eacc..f559743dd 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -429,6 +429,8 @@ + + From f2a012c5c09e3cf8f5a56405c98bd60c4aa7c383 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 13 Feb 2026 10:24:10 -0600 Subject: [PATCH 049/240] feat(dashboard-render): improve dashboard loading logic and enhance error handling for filters Signed-off-by: Manuel Abascal --- .../dashboard-render.component.html | 8 ++--- .../dashboard-render.component.ts | 29 ++++++++++++++----- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html b/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html index f99dcb24d..5372cdeec 100644 --- a/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html +++ b/frontend/src/app/dashboard/dashboard-render/dashboard-render.component.html @@ -1,5 +1,5 @@
-
+
{{dashboard.name}}
@@ -17,9 +17,9 @@
{{dashboard.name}}
- + {{dashboard.name}}
-
{ - this.dashboard = this.layoutService.dashboard; - this.filters = this.dashboard && this.dashboard.filters ? JSON.parse(this.dashboard.filters) : []; - this.loadingVisualizations = false; - }), - map(data => data.response) + + this.layout$ = this.activatedRoute.data.pipe( + map(data => { + const response = data && data.response ? data.response : []; + + this.dashboard = this.layoutService.dashboard ? this.layoutService.dashboard : null; + + try { + this.filters = this.dashboard && this.dashboard.filters + ? JSON.parse(this.dashboard.filters) + : []; + } catch { + this.filters = []; + } + + this.loadingVisualizations = false; + + return response; + }) ); + this.dashboardBehavior.$filterDashboard.subscribe(dashboardFilter => { if (dashboardFilter) { mergeParams(dashboardFilter.filter, this.filtersValues).then(newFilters => { From 383db289195818d9e94c4b23ab231198fca764ea Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 09:55:36 -0600 Subject: [PATCH 050/240] feat: implement gRPC client and service for collector management --- .../config/CollectorConfiguration.java | 15 +- .../utmstack/config/GrpcConfiguration.java | 2 - .../grpc/client/CollectorServiceClient.java | 94 +++++++++ .../client/PanelCollectorServiceClient.java | 38 ++++ .../grpc/connection/GrpcConnection.java | 34 ++++ .../interceptor/CollectorAuthInterceptor.java | 45 +++++ .../GrpcInternalKeyInterceptor.java | 25 +++ .../collectors/CollectorOpsService.java | 178 ++++++------------ .../rest/collectors/UtmCollectorResource.java | 12 +- backend/src/main/proto/collector.proto | 87 +++++++++ 10 files changed, 389 insertions(+), 141 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java create mode 100644 backend/src/main/proto/collector.proto diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java index ec16e1cee..cb37b9f36 100644 --- a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java @@ -1,15 +1,12 @@ package com.park.utmstack.config; -import com.utmstack.grpc.connection.GrpcConnection; -import com.utmstack.grpc.exception.GrpcConnectionException; -import com.utmstack.grpc.jclient.config.interceptors.impl.GrpcEmptyAuthInterceptor; +import com.park.utmstack.grpc.connection.GrpcConnection; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CollectorConfiguration { - private GrpcConnection collectorConnection; @Value("${grpc.server.address}") private String serverAddress; @@ -18,9 +15,11 @@ public class CollectorConfiguration { private Integer serverPort; @Bean - public GrpcConnection collectorConnection() throws GrpcConnectionException { - this.collectorConnection = new GrpcConnection(); - this.collectorConnection.createChannel(serverAddress, serverPort, new GrpcEmptyAuthInterceptor()); - return this.collectorConnection; + public GrpcConnection collectorConnection() { + + GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); + collectorConnection.connect(); + + return collectorConnection; } } diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 8053115cf..93f6d97bd 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -2,7 +2,6 @@ import com.park.utmstack.security.GrpcInterceptor; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.netty.GrpcSslContexts; import io.grpc.netty.NettyChannelBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; @@ -13,7 +12,6 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; -@Configuration public class GrpcConfiguration { private ManagedChannel channel; diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java new file mode 100644 index 000000000..1760c6005 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -0,0 +1,94 @@ +package com.park.utmstack.grpc.client; + +import agent.CollectorOuterClass; +import agent.CollectorServiceGrpc; +import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; +import com.park.utmstack.service.grpc.AuthResponse; +import com.park.utmstack.service.grpc.DeleteRequest; +import com.park.utmstack.service.grpc.ListRequest; +import com.park.utmstack.util.exceptions.ApiException; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; + +@Slf4j +public class CollectorServiceClient { + + private final ManagedChannel channel; + private final CollectorServiceGrpc.CollectorServiceBlockingStub baseStub; + + public CollectorServiceClient(ManagedChannel channel) { + this.channel = channel; + this.baseStub = CollectorServiceGrpc.newBlockingStub(channel); + } + + public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { + String ctx = "CollectorServiceClient.listCollectors"; + try { + + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.listCollector(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + public AuthResponse deleteCollector(int collectorId, String collectorKey) { + + try { + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors( + new CollectorAuthInterceptor( + String.valueOf(collectorId), + collectorKey + ) + ); + + DeleteRequest request = DeleteRequest.newBuilder() + .setDeletedBy(String.valueOf(collectorId)) + .build(); + + return stub.deleteCollector(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while deleting collector:{}", collectorId, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error deleting collector", collectorId), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + + public CollectorOuterClass.CollectorConfig getCollectorConfig(int collectorId, String collectorKey, CollectorOuterClass.CollectorModule module) { + + try { + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors( + new CollectorAuthInterceptor( + String.valueOf(collectorId), + collectorKey + ) + ); + + CollectorOuterClass.ConfigRequest request = + CollectorOuterClass.ConfigRequest.newBuilder() + .setModule(module) + .build(); + + return stub.getCollectorConfig(request); + + } catch (StatusRuntimeException e) { + log.error("{}: An error occurred while getting collector:{}", collectorId, e.getMessage()); + throw new ApiException(String.format("%s: gRPC error getting collector config", collectorId), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + public void shutdown() { + channel.shutdown(); + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java new file mode 100644 index 000000000..d1f8cd1f3 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -0,0 +1,38 @@ +package com.park.utmstack.grpc.client; + +import agent.CollectorOuterClass; +import agent.PanelCollectorServiceGrpc; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; + +public class PanelCollectorServiceClient { + + private final ManagedChannel channel; + private final PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub baseStub; + + public PanelCollectorServiceClient(ManagedChannel channel) { + this.channel = channel; + this.baseStub = PanelCollectorServiceGrpc.newBlockingStub(channel); + } + + public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { + + try { + PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.registerCollectorConfig(config); + + } catch (StatusRuntimeException e) { + throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); + } catch (Exception e) { + throw new RuntimeException("Unexpected error inserting collector config: " + e.getMessage(), e); + } + } + + public void shutdown() { + channel.shutdown(); + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java b/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java new file mode 100644 index 000000000..93c42aede --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/connection/GrpcConnection.java @@ -0,0 +1,34 @@ +package com.park.utmstack.grpc.connection; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class GrpcConnection { + + private ManagedChannel channel; + private final String host; + private final int port; + + public void connect() { + this.channel = ManagedChannelBuilder + .forAddress(host, port) + .usePlaintext() + .build(); + } + + public ManagedChannel getChannel() { + if (channel == null) { + throw new IllegalStateException("Channel not initialized. Call connect() first."); + } + return channel; + } + + public void shutdown() { + if (channel != null && !channel.isShutdown()) { + channel.shutdown(); + } + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java new file mode 100644 index 000000000..701a568fb --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/interceptor/CollectorAuthInterceptor.java @@ -0,0 +1,45 @@ +package com.park.utmstack.grpc.interceptor; + +import io.grpc.*; + +public class CollectorAuthInterceptor implements ClientInterceptor { + + private static final Metadata.Key ID_HEADER = + Metadata.Key.of("id", Metadata.ASCII_STRING_MARSHALLER); + + private static final Metadata.Key KEY_HEADER = + Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER); + + private static final Metadata.Key TYPE_HEADER = + Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); + + private final String collectorId; + private final String collectorKey; + + public CollectorAuthInterceptor(String collectorId, String collectorKey) { + this.collectorId = collectorId; + this.collectorKey = collectorKey; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, + CallOptions callOptions, + Channel channel) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>( + channel.newCall(methodDescriptor, callOptions)) { + + @Override + public void start(Listener responseListener, Metadata headers) { + + headers.put(ID_HEADER, collectorId); + headers.put(KEY_HEADER, collectorKey); + headers.put(TYPE_HEADER, "collector"); + + super.start(responseListener, headers); + } + }; + } +} + diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java new file mode 100644 index 000000000..eb620fd2a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java @@ -0,0 +1,25 @@ +package com.park.utmstack.grpc.interceptor; + +import com.park.utmstack.config.Constants; +import io.grpc.*; + +public class GrpcInternalKeyInterceptor implements ClientInterceptor { + + private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); + + @Override + public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>( + channel.newCall(methodDescriptor, callOptions)) { + + @Override public void start(Listener responseListener, Metadata headers) { + String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); + + headers.put(INTERNAL_KEY_HEADER, internalKey); + headers.put(TYPE_HEADER, "internal"); + super.start(responseListener, headers); + } }; + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index a18fae336..0d3263a5f 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -8,10 +8,6 @@ import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; import agent.CollectorOuterClass.ConfigRequest; -import agent.Common; -import agent.Common.ListRequest; -import agent.Common.AuthResponse; -import agent.Common.DeleteRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -19,6 +15,9 @@ import com.park.utmstack.domain.collector.UtmCollector; import com.park.utmstack.domain.network_scan.AssetGroupFilter; import com.park.utmstack.domain.network_scan.UtmAssetGroup; +import com.park.utmstack.grpc.client.CollectorServiceClient; +import com.park.utmstack.grpc.client.PanelCollectorServiceClient; +import com.park.utmstack.grpc.connection.GrpcConnection; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; @@ -33,21 +32,24 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.grpc.AuthResponse; +import com.park.utmstack.service.grpc.DeleteRequest; +import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; +import com.park.utmstack.util.exceptions.ApiException; import com.park.utmstack.web.rest.errors.BadRequestAlertException; -import com.utmstack.grpc.connection.GrpcConnection; import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; import com.utmstack.grpc.service.CollectorService; -import com.utmstack.grpc.service.PanelCollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -69,8 +71,8 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); private final GrpcConnection grpcConnection; - private final PanelCollectorService panelCollectorService; - private final CollectorService collectorService; + private final PanelCollectorServiceClient panelCollectorService; + private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; private final UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository; @@ -100,9 +102,10 @@ public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleService utmModuleService, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { + this.grpcConnection = grpcConnection; - this.panelCollectorService = new PanelCollectorService(grpcConnection); - this.collectorService = new CollectorService(grpcConnection); + this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); + this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; @@ -130,35 +133,15 @@ public ConfigKnowledge upsertCollectorConfig(CollectorConfig config) throws Coll } try { - return panelCollectorService.insertCollectorConfig(config, internalKey); + return panelCollectorService.insertCollectorConfig(config); } catch (Exception e) { String msg = ctx + ": " + e.getMessage(); throw new CollectorConfigurationGrpcException(msg); } } - /** - * Method to get collectors list. - * - * @param request is the request with all the pagination and search params used to list collectors - * according to those params. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. - */ - public ListCollectorsResponseDTO listCollector(ListRequest request) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".listCollector"; - - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - if (!StringUtils.hasText(internalKey)) { - throw new BadRequestAlertException(ctx + ": Internal key not configured.", ctx, CLASSNAME); - } - - try { - return mapToListCollectorsResponseDTO(collectorService.listCollector(request, internalKey)); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public ListCollectorsResponseDTO listCollector(ListRequest request) { + return mapToListCollectorsResponseDTO(collectorService.listCollectors(request)); } /** @@ -178,10 +161,10 @@ public CollectorHostnames listCollectorHostnames(ListRequest request) throws Col } try { - ListCollectorResponse response = collectorService.listCollector(request, internalKey); + ListCollectorResponse response = collectorService.listCollectors(request); CollectorHostnames collectorHostnames = new CollectorHostnames(); - response.getRowsList().forEach(c->{ + response.getRowsList().forEach(c -> { collectorHostnames.getHostname().add(c.getHostname()); }); @@ -196,48 +179,20 @@ public CollectorHostnames listCollectorHostnames(ListRequest request) throws Col * Method to get collectors by hostname and module. * * @param request contains the filter information used to search. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. */ - public ListCollectorsResponseDTO getCollectorsByHostnameAndModule(ListRequest request) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".GetCollectorsByHostnameAndModule"; - - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - if (!StringUtils.hasText(internalKey)) { - throw new BadRequestAlertException(ctx + ": Internal key not configured.", ctx, CLASSNAME); - } - - try { - return mapToListCollectorsResponseDTO(collectorService.listCollector(request, internalKey)); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public ListCollectorsResponseDTO getCollectorsByHostnameAndModule(ListRequest request) { + return mapToListCollectorsResponseDTO(collectorService.listCollectors(request)); } - /** - * Method to get a collector config from agent manager via gRPC. - * - * @param request represents the CollectorModule to get the configurations from. - * @param auth is the authentication parameters used to filter in order to get the collector configuration. - * @throws CollectorServiceGrpcException if the action can't be performed or the request is malformed. - */ - public CollectorConfig getCollectorConfig(ConfigRequest request, AuthResponse auth) throws CollectorServiceGrpcException { - final String ctx = CLASSNAME + ".getCollectorConfig"; - - - try { - return collectorService.requestCollectorConfig(request, auth); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - throw new CollectorServiceGrpcException(msg); - } + public CollectorConfig getCollectorConfig(CollectorDTO collectorDTO) { + return collectorService.getCollectorConfig(collectorDTO.getId(), collectorDTO.getCollectorKey(), + CollectorModule.valueOf(collectorDTO.getModule().toString())); } /** * Method to transform a ListCollectorResponse to ListCollectorsResponseDTO */ - private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorResponse response) throws Exception { + private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorResponse response) { final String ctx = CLASSNAME + ".mapToListCollectorsResponseDTO"; try { ListCollectorsResponseDTO dto = new ListCollectorsResponseDTO(); @@ -253,7 +208,7 @@ private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(ListCollectorRe return dto; } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); + throw new ApiException(String.format("%s: Error mapping ListCollectorResponse to ListCollectorsResponseDTO: %s", ctx, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -321,50 +276,40 @@ private CollectorGroupConfigurations mapToCollectorGroupConfigurations(UtmModule */ public void deleteCollector(String hostname, CollectorModuleEnum module) { final String ctx = CLASSNAME + ".deleteCollector"; - try { - String currentUser = SecurityUtils.getCurrentUserLogin().orElseThrow(() -> new RuntimeException("No current user login")); - - Optional collectorToSearch = getCollectorsByHostnameAndModule( - getListRequestByHostnameAndModule(hostname, module)).getCollectors() - .stream().findFirst(); - try { - if (collectorToSearch.isEmpty()) { - log.error(String.format("%1$s: UtmCollector %2$s could not be deleted because no information was obtained from collector manager", ctx, hostname)); - return; - } - } catch (StatusRuntimeException e) { - if (e.getStatus().getCode() == Status.Code.NOT_FOUND) { - log.error(String.format("%1$s: UtmCollector %2$s could not be deleted because was not found", ctx, hostname)); - return; - } - } - DeleteRequest collectorDelete = DeleteRequest.newBuilder().setDeletedBy(currentUser).build(); - AuthResponse auth = Common.AuthResponse.newBuilder() - .setId(collectorToSearch.get().getId()) - .setKey(collectorToSearch.get().getCollectorKey()) - .build(); - collectorService.deleteCollector(collectorDelete, auth); + var request = getListRequestByHostnameAndModule(hostname, module); + List collectors = getCollectorsByHostnameAndModule(request).getCollectors(); - } catch (Exception e) { - String msg = ctx + ": " + e.getLocalizedMessage(); - log.error(msg); - throw new RuntimeException(msg); + Optional found = collectors.stream().findFirst(); + if (found.isEmpty()) { + log.error("{}: Collector {} not found in Agent Manager", ctx, hostname); + return; } + + CollectorDTO collector = found.get(); + + collectorService.deleteCollector( + collector.getId(), + collector.getCollectorKey() + ); + + log.info("{}: Collector {} deleted successfully", ctx, hostname); + } - public List mapPasswordConfiguration( List configs) { - return configs.stream().peek(config -> { + public List mapPasswordConfiguration(List configs) { + + return configs.stream().peek(config -> { if (config.getConfDataType().equals("password")) { final UtmModuleGroupConfiguration utmModuleGroupConfiguration = utmModuleGroupConfigurationRepository.findById(config.getId()) .orElseThrow(() -> new RuntimeException(String.format("Configuration id %s not found", config.getId()))); - if (config.getConfValue().equals(utmModuleGroupConfiguration.getConfValue())){ + if (config.getConfValue().equals(utmModuleGroupConfiguration.getConfValue())) { config.setConfValue(CipherUtil.decrypt(utmModuleGroupConfiguration.getConfValue(), ENCRYPTION_KEY)); } } - }).collect(Collectors.toList()); + }).collect(Collectors.toList()); } @Transactional @@ -377,6 +322,7 @@ public void updateGroup(List collectorsIds, Long assetGroupId) throws Exce throw new Exception(ctx + ": " + e.getMessage()); } } + @Transactional public Page searchGroupsByFilter(AssetGroupFilter filter, Pageable pageable) throws Exception { final String ctx = CLASSNAME + ".searchGroupsByFilter"; @@ -492,20 +438,20 @@ public void deleteCollector(Long id) throws Exception { this.deleteCollector(collector.get().getHostname(), CollectorModuleEnum.valueOf(collector.get().getModule())); List modules = this.utmModuleGroupRepository.findAllByCollector(id.toString()); - if(!modules.isEmpty()){ + if (!modules.isEmpty()) { UtmModule module = utmModuleRepository.findById(modules.get(0).getModuleId()).get(); - if(module.getModuleActive()){ + if (module.getModuleActive()) { modules = this.utmModuleGroupRepository.findAllByModuleId(module.getId()) - .stream().filter( m -> !m.getCollector().equals(id.toString())) + .stream().filter(m -> !m.getCollector().equals(id.toString())) .toList(); - if(modules.isEmpty()){ + if (modules.isEmpty()) { this.utmModuleService.activateDeactivate(ModuleActivationDTO.builder() - .serverId(module.getServerId()) - .moduleName(module.getModuleName()) - .activationStatus(false) + .serverId(module.getServerId()) + .moduleName(module.getModuleName()) + .activationStatus(false) .build()); } } @@ -528,14 +474,7 @@ public String validateCollectorConfig(CollectorConfigKeysDTO collectorConfig) { } public CollectorConfig cacheCurrentCollectorConfig(CollectorDTO collectorDTO) throws CollectorServiceGrpcException { - return this.getCollectorConfig( - ConfigRequest.newBuilder() - .setModule(CollectorModule.valueOf(collectorDTO.getModule().toString())) - .build(), - AuthResponse.newBuilder() - .setId(collectorDTO.getId()) - .setKey(collectorDTO.getCollectorKey()) - .build()); + return this.getCollectorConfig(collectorDTO); } public void updateCollectorConfigViaGrpc( @@ -552,10 +491,10 @@ public void updateCollectorConfigurationKeys(CollectorConfigKeysDTO collectorCon try { List configs = utmModuleGroupRepository .findAllByModuleIdAndCollector(collectorConfig.getModuleId(), - String.valueOf(collectorConfig.getCollector().getId())); + String.valueOf(collectorConfig.getCollector().getId())); List keys = collectorConfig.getKeys(); - if (CollectionUtils.isEmpty(collectorConfig.getKeys())){ + if (CollectionUtils.isEmpty(collectorConfig.getKeys())) { utmModuleGroupRepository.deleteAll(configs); } else { for (UtmModuleGroupConfiguration key : keys) { @@ -590,12 +529,11 @@ public ListRequest getListRequestByHostnameAndModule(String hostname, CollectorM } else if (module != null) { query = "module.Is=" + module.name(); } - ListRequest request = ListRequest.newBuilder() + return ListRequest.newBuilder() .setPageNumber(1) .setPageSize(1000000) .setSearchQuery(query) .setSortBy("id,desc") .build(); - return request; } } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index fd962beae..441bc476c 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -1,7 +1,6 @@ package com.park.utmstack.web.rest.collectors; import agent.CollectorOuterClass.CollectorConfig; -import agent.Common.ListRequest; import com.park.utmstack.domain.application_events.enums.ApplicationEventType; import com.park.utmstack.domain.application_modules.UtmModuleGroup; import com.park.utmstack.domain.network_scan.AssetGroupFilter; @@ -19,6 +18,7 @@ import com.park.utmstack.service.dto.collectors.dto.ErrorResponse; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.util.ResponseUtil; import com.park.utmstack.web.rest.errors.BadRequestAlertException; import com.park.utmstack.web.rest.errors.InternalServerErrorException; @@ -143,11 +143,6 @@ public ResponseEntity listCollectorsByModule(@Request log.error(msg); applicationEventService.createEvent(msg, ApplicationEventType.ERROR); return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } catch (CollectorServiceGrpcException e) { - String msg = ctx + ": UtmCollector manager is not available or was an error getting the collector list. " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_GATEWAY, msg); } } @@ -208,11 +203,6 @@ public ResponseEntity listCollectorByHostNameAndModul log.error(msg); applicationEventService.createEvent(msg, ApplicationEventType.ERROR); return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } catch (CollectorServiceGrpcException e) { - String msg = ctx + ": UtmCollector manager is not available or was an error getting configuration. " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_GATEWAY, msg); } } diff --git a/backend/src/main/proto/collector.proto b/backend/src/main/proto/collector.proto new file mode 100644 index 000000000..99fd4d551 --- /dev/null +++ b/backend/src/main/proto/collector.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +option go_package = "github.com/utmstack/UTMStack/agent-manager/agent"; +import "common.proto"; + +package agent; + +service CollectorService { + rpc RegisterCollector(RegisterRequest) returns (AuthResponse) {} + rpc DeleteCollector(DeleteRequest) returns (AuthResponse) {} + rpc ListCollector (ListRequest) returns (ListCollectorResponse) {} + rpc CollectorStream(stream CollectorMessages) returns (stream CollectorMessages) {} + rpc GetCollectorConfig (ConfigRequest) returns (CollectorConfig) {} +} + +service PanelCollectorService { + rpc RegisterCollectorConfig(CollectorConfig) returns (ConfigKnowledge) {} +} + +enum CollectorModule{ + AS_400 = 0; + UTMSTACK = 1; +} + +message RegisterRequest { + string ip = 1; + string hostname = 2; + string version = 3; + CollectorModule collector = 4; +} + +message ListCollectorResponse { + repeated Collector rows = 1; + int32 total = 2; +} + +message Collector { + int32 id = 1; + Status status = 2; + string collector_key = 3; + string ip = 4; + string hostname = 5; + string version = 6; + CollectorModule module = 7; + string last_seen = 8; +} + +message CollectorMessages { + oneof stream_message { + CollectorConfig config = 1; + ConfigKnowledge result = 2; + } +} + +message CollectorConfig { + string collector_id = 1; + repeated CollectorConfigGroup groups = 2; + string request_id = 3; +} + +message CollectorConfigGroup { + int32 id = 1; + string group_name = 2; + string group_description = 3; + repeated CollectorGroupConfigurations configurations = 4; + int32 collector_id = 5; +} + +message CollectorGroupConfigurations { + int32 id = 1; + int32 group_id = 2; + string conf_key = 3; + string conf_value = 4; + string conf_name = 5; + string conf_description = 6; + string conf_data_type = 7; + bool conf_required = 8; +} + +message ConfigKnowledge{ + string accepted = 1; + string request_id = 2; +} + +message ConfigRequest { + CollectorModule module = 1; +} From fd74bc8aed60384cac0e668ad605d7d11d02bb2b Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 10:10:53 -0600 Subject: [PATCH 051/240] feat: implement gRPC client and service for collector management --- .../main/java/com/park/utmstack/config/GrpcConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 93f6d97bd..9d014b830 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -12,6 +12,7 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; +@Configuration public class GrpcConfiguration { private ManagedChannel channel; From 0e67c500bf5a5dc6b5ce1f1e4c3f28c298b766ea Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 11:10:51 -0600 Subject: [PATCH 052/240] feat: implement gRPC client and service for collector management --- .../service/collectors/CollectorOpsService.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 0d3263a5f..78b855cf8 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -7,7 +7,6 @@ import agent.CollectorOuterClass.Collector; import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; -import agent.CollectorOuterClass.ConfigRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -22,7 +21,6 @@ import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.repository.collector.UtmCollectorRepository; -import com.park.utmstack.security.SecurityUtils; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.application_modules.UtmModuleService; import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; @@ -32,8 +30,6 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; -import com.park.utmstack.service.grpc.AuthResponse; -import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; @@ -42,7 +38,6 @@ import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; -import com.utmstack.grpc.service.CollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; @@ -70,7 +65,7 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); - private final GrpcConnection grpcConnection; + private final ManagedChannel channel; private final PanelCollectorServiceClient panelCollectorService; private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; @@ -92,7 +87,7 @@ public class CollectorOpsService { private final CollectorValidatorService collectorValidatorService; - public CollectorOpsService(GrpcConnection grpcConnection, + public CollectorOpsService(ManagedChannel channel, UtmModuleGroupService moduleGroupService, UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository, UtmCollectorRepository utmCollectorRepository, @@ -103,9 +98,9 @@ public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { - this.grpcConnection = grpcConnection; - this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); - this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); + this.channel = channel; + this.panelCollectorService = new PanelCollectorServiceClient(channel); + this.collectorService = new CollectorServiceClient(channel); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; From f204e48b0968f1a8b4453c7937223bafbd083153 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 12:22:21 -0600 Subject: [PATCH 053/240] feat: remove unused GrpcInternalKeyInterceptor from collector service clients --- .../config/CollectorConfiguration.java | 25 ------------------- .../grpc/client/CollectorServiceClient.java | 8 +----- .../client/PanelCollectorServiceClient.java | 6 +---- .../GrpcInternalKeyInterceptor.java | 25 ------------------- 4 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java delete mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java deleted file mode 100644 index cb37b9f36..000000000 --- a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.config; - -import com.park.utmstack.grpc.connection.GrpcConnection; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class CollectorConfiguration { - - @Value("${grpc.server.address}") - private String serverAddress; - - @Value("${grpc.server.port}") - private Integer serverPort; - - @Bean - public GrpcConnection collectorConnection() { - - GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); - collectorConnection.connect(); - - return collectorConnection; - } -} diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java index 1760c6005..faa50bbc6 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -3,7 +3,6 @@ import agent.CollectorOuterClass; import agent.CollectorServiceGrpc; import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import com.park.utmstack.service.grpc.AuthResponse; import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; @@ -27,12 +26,7 @@ public CollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { String ctx = "CollectorServiceClient.listCollectors"; try { - - CollectorServiceGrpc.CollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.listCollector(request); - + return baseStub.listCollector(request); } catch (StatusRuntimeException e) { log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java index d1f8cd1f3..db0e30e76 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -2,7 +2,6 @@ import agent.CollectorOuterClass; import agent.PanelCollectorServiceGrpc; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; @@ -19,10 +18,7 @@ public PanelCollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { try { - PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.registerCollectorConfig(config); + return baseStub.registerCollectorConfig(config); } catch (StatusRuntimeException e) { throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java deleted file mode 100644 index eb620fd2a..000000000 --- a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.grpc.interceptor; - -import com.park.utmstack.config.Constants; -import io.grpc.*; - -public class GrpcInternalKeyInterceptor implements ClientInterceptor { - - private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); - private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); - - @Override - public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { - - return new ForwardingClientCall.SimpleForwardingClientCall<>( - channel.newCall(methodDescriptor, callOptions)) { - - @Override public void start(Listener responseListener, Metadata headers) { - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - headers.put(INTERNAL_KEY_HEADER, internalKey); - headers.put(TYPE_HEADER, "internal"); - super.start(responseListener, headers); - } }; - } -} From 5315e0658372345e5bff50513301b4d966cc4e77 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 13:41:11 -0600 Subject: [PATCH 054/240] feat: add CollectorConfigDTO and unique server name validation --- .../validators/UniqueServerName.java | 18 +++++++++ .../validators/UniqueServerNameValidator.java | 27 +++++++++++++ .../collectors/dto/CollectorConfigDTO.java | 27 +++++++++++++ .../dto/CollectorConfigKeysDTO.java | 39 ------------------- 4 files changed, 72 insertions(+), 39 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java create mode 100644 backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java create mode 100644 backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java delete mode 100644 backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java diff --git a/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java new file mode 100644 index 000000000..de04fc86a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerName.java @@ -0,0 +1,18 @@ +package com.park.utmstack.domain.collector.validators; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = UniqueServerNameValidator.class) +public @interface UniqueServerName { + String message() default "Server name must be unique."; + Class[] groups() default {}; + Class[] payload() default {}; +} + diff --git a/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java new file mode 100644 index 000000000..437fcbcd1 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java @@ -0,0 +1,27 @@ +package com.park.utmstack.domain.collector.validators; + +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; +import java.util.stream.Collectors; + +public class UniqueServerNameValidator implements ConstraintValidator> { + + @Override + public boolean isValid(List keys, ConstraintValidatorContext context) { + + if (keys == null || keys.isEmpty()) return false; + + long duplicates = keys.stream() + .filter(k -> "Hostname".equals(k.getConfName())) + .collect(Collectors.groupingBy(UtmModuleGroupConfiguration::getConfValue, Collectors.counting())) + .values().stream() + .filter(count -> count > 1) + .count(); + + return duplicates == 0; + + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java new file mode 100644 index 000000000..562d1bad6 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java @@ -0,0 +1,27 @@ +package com.park.utmstack.service.dto.collectors.dto; + +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; +import com.park.utmstack.domain.collector.validators.UniqueServerName; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Setter +@Getter +public class CollectorConfigDTO { + + @NotNull + CollectorDTO collector; + + @NotNull + private Long moduleId; + + @NotNull + @NotEmpty + @UniqueServerName + private List keys; + +} diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java deleted file mode 100644 index 40af52707..000000000 --- a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.park.utmstack.service.dto.collectors.dto; - -import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; - -import javax.validation.constraints.NotNull; -import java.util.List; - -public class CollectorConfigKeysDTO { - @NotNull - CollectorDTO collector; - @NotNull - private Long moduleId; - @NotNull - private List keys; - - public Long getModuleId() { - return moduleId; - } - - public void setModuleId(Long moduleId) { - this.moduleId = moduleId; - } - - public List getKeys() { - return keys; - } - - public void setKeys(List keys) { - this.keys = keys; - } - - public CollectorDTO getCollector() { - return collector; - } - - public void setCollector(CollectorDTO collector) { - this.collector = collector; - } -} From 83cba826839e976f2e5ebeb2eb1fc49a2ceed4fb Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:17:10 -0600 Subject: [PATCH 055/240] feat: add CollectorConfigBuilder for constructing CollectorConfig from DTO --- .../collectors/CollectorConfigBuilder.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java new file mode 100644 index 000000000..cd821aa30 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigBuilder.java @@ -0,0 +1,108 @@ +package com.park.utmstack.service.collectors; + +import agent.CollectorOuterClass.CollectorConfig; +import agent.CollectorOuterClass.CollectorConfigGroup; +import agent.CollectorOuterClass.CollectorGroupConfigurations; +import com.park.utmstack.config.Constants; +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; +import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; +import com.park.utmstack.service.application_modules.UtmModuleGroupService; +import com.park.utmstack.util.CipherUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Component +@RequiredArgsConstructor +public class CollectorConfigBuilder { + + private final UtmModuleGroupService moduleGroupService; + private final UtmModuleGroupConfigurationRepository configRepo; + + public CollectorConfig build(CollectorConfigDTO dto) { + + List processed = processPasswords(dto.getKeys()); + + return buildCollectorConfig(processed, dto.getCollector()); + } + + + private List processPasswords(List configs) { + + return configs.stream().map(config -> { + + if (Constants.CONF_TYPE_PASSWORD.equals(config.getConfDataType())) { + + UtmModuleGroupConfiguration original = configRepo.findById(config.getId()) + .orElseThrow(() -> new RuntimeException("Configuration id " + config.getId() + " not found")); + + if (Objects.equals(config.getConfValue(), original.getConfValue())) { + config.setConfValue( + CipherUtil.decrypt(original.getConfValue(), System.getenv(Constants.ENV_ENCRYPTION_KEY)) + ); + } + } + + return config; + + }).toList(); + } + + + private CollectorConfig buildCollectorConfig(List keys, CollectorDTO collectorDTO) { + + List groupIds = keys.stream() + .map(UtmModuleGroupConfiguration::getGroupId) + .distinct() + .toList(); + + List groups = new ArrayList<>(); + + for (Long groupId : groupIds) { + + moduleGroupService.findOne(groupId).ifPresent(group -> { + + List configs = + keys.stream() + .filter(k -> k.getGroupId().equals(groupId)) + .map(this::mapToCollectorGroupConfigurations) + .toList(); + + groups.add( + CollectorConfigGroup.newBuilder() + .setGroupName(group.getGroupName()) + .setGroupDescription(group.getGroupDescription()) + .addAllConfigurations(configs) + .setCollectorId(collectorDTO.getId()) + .build() + ); + }); + } + + return CollectorConfig.newBuilder() + .setCollectorId(String.valueOf(collectorDTO.getId())) + .setRequestId(String.valueOf(System.currentTimeMillis())) + .addAllGroups(groups) + .build(); + } + + + private CollectorGroupConfigurations mapToCollectorGroupConfigurations( + UtmModuleGroupConfiguration c) { + + return CollectorGroupConfigurations.newBuilder() + .setConfKey(c.getConfKey()) + .setConfName(c.getConfName()) + .setConfDescription(c.getConfDescription()) + .setConfDataType(c.getConfDataType()) + .setConfValue(c.getConfValue()) + .setConfRequired(c.getConfRequired()) + .build(); + } +} + From c4f587e49e9377c5afe285dfccd42fb802fe719b Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:17:34 -0600 Subject: [PATCH 056/240] feat: add CollectorGrpcService for managing collector operations via gRPC --- .../collectors/CollectorGrpcService.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java new file mode 100644 index 000000000..073900217 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorGrpcService.java @@ -0,0 +1,36 @@ +package com.park.utmstack.service.collectors; + +import agent.CollectorOuterClass.*; +import com.park.utmstack.grpc.client.CollectorServiceClient; +import com.park.utmstack.grpc.client.PanelCollectorServiceClient; +import com.park.utmstack.service.grpc.ListRequest; +import io.grpc.ManagedChannel; +import org.springframework.stereotype.Service; + +@Service +public class CollectorGrpcService { + + private final CollectorServiceClient collectorClient; + private final PanelCollectorServiceClient panelClient; + + public CollectorGrpcService(ManagedChannel channel) { + this.collectorClient = new CollectorServiceClient(channel); + this.panelClient = new PanelCollectorServiceClient(channel); + } + + public ListCollectorResponse listCollectors(ListRequest request) { + return collectorClient.listCollectors(request); + } + + public CollectorConfig getCollectorConfig(int id, String key, CollectorModule module) { + return collectorClient.getCollectorConfig(id, key, module); + } + + public void deleteCollector(int id, String key) { + collectorClient.deleteCollector(id, key); + } + + public ConfigKnowledge upsertCollectorConfig(CollectorConfig config) { + return panelClient.insertCollectorConfig(config); + } +} From 2ec19e5f2c6252dcdf690305528de435c49f5f80 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:05 -0600 Subject: [PATCH 057/240] feat: update CollectorConfig validation and add CollectorService for gRPC integration --- .../collectors/CollectorOpsService.java | 9 +++--- .../service/collectors/CollectorService.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 78b855cf8..df0ef0452 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -16,7 +16,6 @@ import com.park.utmstack.domain.network_scan.UtmAssetGroup; import com.park.utmstack.grpc.client.CollectorServiceClient; import com.park.utmstack.grpc.client.PanelCollectorServiceClient; -import com.park.utmstack.grpc.connection.GrpcConnection; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; @@ -26,7 +25,7 @@ import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; import com.park.utmstack.service.dto.collectors.CollectorHostnames; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; @@ -458,7 +457,7 @@ public void deleteCollector(Long id) throws Exception { } - public String validateCollectorConfig(CollectorConfigKeysDTO collectorConfig) { + public String validateCollectorConfig(CollectorConfigDTO collectorConfig) { Errors errors = new BeanPropertyBindingResult(collectorConfig, "updateConfigurationKeysBody"); collectorValidatorService.validate(collectorConfig, errors); @@ -473,7 +472,7 @@ public CollectorConfig cacheCurrentCollectorConfig(CollectorDTO collectorDTO) th } public void updateCollectorConfigViaGrpc( - CollectorConfigKeysDTO collectorConfig, + CollectorConfigDTO collectorConfig, CollectorDTO collectorDTO) throws CollectorConfigurationGrpcException { this.upsertCollectorConfig( @@ -481,7 +480,7 @@ public void updateCollectorConfigViaGrpc( this.mapPasswordConfiguration(collectorConfig.getKeys()), collectorDTO)); } - public void updateCollectorConfigurationKeys(CollectorConfigKeysDTO collectorConfig) throws Exception { + public void updateCollectorConfigurationKeys(CollectorConfigDTO collectorConfig) throws Exception { final String ctx = CLASSNAME + ".updateCollectorConfigurationKeys"; try { List configs = utmModuleGroupRepository diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java new file mode 100644 index 000000000..bbffd535a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java @@ -0,0 +1,28 @@ +package com.park.utmstack.service.collectors; + +import agent.CollectorOuterClass; +import com.park.utmstack.service.application_events.ApplicationEventService; +import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CollectorService { + + private final CollectorGrpcService collectorGrpcService; + private final ApplicationEventService eventService; + private final UtmModuleGroupConfigurationService moduleGroupConfigurationService; + private final CollectorConfigBuilder CollectorConfigBuilder; + + public void upsertCollectorConfig(CollectorConfigDTO collectorConfig) { + + this.moduleGroupConfigurationService.updateConfigurationKeys(collectorConfig.getModuleId(), collectorConfig.getKeys()); + + CollectorOuterClass.CollectorConfig collector = CollectorConfigBuilder.build(collectorConfig); + collectorGrpcService.upsertCollectorConfig(collector); + } + +} + From cd763dc153996de137a744e74e2747a39d6dfce0 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:29 -0600 Subject: [PATCH 058/240] feat: update CollectorValidatorService to use CollectorConfigDTO for validation --- .../validators/collector/CollectorValidatorService.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java b/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java index ceaccbfaf..02d1bca1b 100644 --- a/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java +++ b/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java @@ -1,8 +1,7 @@ package com.park.utmstack.service.validators.collector; import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; -import com.park.utmstack.web.rest.application_modules.UtmModuleGroupConfigurationResource; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import org.springframework.stereotype.Service; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -14,12 +13,12 @@ public class CollectorValidatorService implements Validator { @Override public boolean supports(Class clazz) { - return CollectorConfigKeysDTO.class.equals(clazz); + return CollectorConfigDTO.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { - CollectorConfigKeysDTO updateConfigurationKeysBody = (CollectorConfigKeysDTO) target; + CollectorConfigDTO updateConfigurationKeysBody = (CollectorConfigDTO) target; Map hostNames = updateConfigurationKeysBody.getKeys().stream() .filter(config -> config.getConfName().equals("Hostname")) From f196f0267a4f38583f3d32da39ba3a4593545973 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:34 -0600 Subject: [PATCH 059/240] feat: refactor UtmCollectorResource to use CollectorConfigDTO and CollectorOpsService --- .../rest/collectors/UtmCollectorResource.java | 77 ++++++------------- 1 file changed, 24 insertions(+), 53 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index 441bc476c..f6dfc8303 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -9,10 +9,11 @@ import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.collectors.CollectorOpsService; +import com.park.utmstack.service.collectors.CollectorService; import com.park.utmstack.service.collectors.UtmCollectorService; import com.park.utmstack.service.dto.collectors.CollectorActionEnum; import com.park.utmstack.service.dto.collectors.CollectorHostnames; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; import com.park.utmstack.service.dto.collectors.dto.ErrorResponse; @@ -27,6 +28,7 @@ import com.park.utmstack.web.rest.util.PaginationUtil; import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; +import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springdoc.api.annotations.ParameterObject; @@ -48,35 +50,20 @@ * REST controller for managing {@link UtmCollectorResource}. */ @RestController +@RequiredArgsConstructor @RequestMapping("/api") public class UtmCollectorResource { private static final String CLASSNAME = "UtmCollectorResource"; - private final CollectorOpsService collectorService; + private final CollectorOpsService collectorOpsService; private final Logger log = LoggerFactory.getLogger(UtmCollectorResource.class); private final ApplicationEventService applicationEventService; private final UtmModuleGroupConfigurationService moduleGroupConfigurationService; - private final UtmModuleGroupService moduleGroupService; - private final ApplicationEventService eventService; - private final UtmCollectorService utmCollectorService; + private final CollectorService collectorService; - public UtmCollectorResource(CollectorOpsService collectorService, - ApplicationEventService applicationEventService, - UtmModuleGroupConfigurationService moduleGroupConfigurationService, - UtmModuleGroupService moduleGroupService, - ApplicationEventService eventService, - UtmCollectorService utmCollectorService) { - - this.collectorService = collectorService; - this.applicationEventService = applicationEventService; - this.moduleGroupConfigurationService = moduleGroupConfigurationService; - this.moduleGroupService = moduleGroupService; - this.eventService = eventService; - this.utmCollectorService = utmCollectorService; - } /** * {@code POST /collector-config} : Create or update the collector configs. @@ -87,27 +74,11 @@ public UtmCollectorResource(CollectorOpsService collectorService, * persist the configurations. */ @PostMapping("/collector-config") - public ResponseEntity upsertCollectorConfig( - @Valid @RequestBody CollectorConfigKeysDTO collectorConfig, - @RequestParam(name = "action", defaultValue = "CREATE") CollectorActionEnum action) { - - final String ctx = CLASSNAME + ".upsertCollectorConfig"; - CollectorConfig cacheConfig = null; + public ResponseEntity upsertCollectorConfig(@Valid @RequestBody CollectorConfigDTO collectorConfig, + @RequestParam(name = "action", defaultValue = "CREATE") CollectorActionEnum action) { - // Validate collector configuration - String validationErrorMessage = this.collectorService.validateCollectorConfig(collectorConfig); - if (validationErrorMessage != null) { - return logAndResponse(new ErrorResponse(validationErrorMessage, HttpStatus.PRECONDITION_FAILED)); - } - - try { - cacheConfig = this.collectorService.cacheCurrentCollectorConfig(collectorConfig.getCollector()); - this.upsert(collectorConfig); - return ResponseEntity.noContent().build(); - - } catch (Exception e) { - return handleUpdateError(e, cacheConfig, collectorConfig.getCollector()); - } + collectorService.upsertCollectorConfig(collectorConfig); + return ResponseEntity.noContent().build(); } /** @@ -134,7 +105,7 @@ public ResponseEntity listCollectorsByModule(@Request .setSortBy(sortBy != null ? sortBy : "") .build(); - ListCollectorsResponseDTO response = collectorService.listCollector(request); + ListCollectorsResponseDTO response = collectorOpsService.listCollector(request); HttpHeaders headers = new HttpHeaders(); headers.add("X-Total-Count", Long.toString(response.getTotal())); return ResponseEntity.ok().headers(headers).body(response); @@ -169,7 +140,7 @@ public ResponseEntity listCollectorHostNames(@RequestParam(r .setSearchQuery(module != null ? "module.Is=" + module : "") .setSortBy(sortBy != null ? sortBy : "") .build(); - return ResponseEntity.ok().body(collectorService.listCollectorHostnames(request)); + return ResponseEntity.ok().body(collectorOpsService.listCollectorHostnames(request)); } catch (BadRequestAlertException e) { String msg = ctx + ": " + e.getLocalizedMessage(); log.error(msg); @@ -196,8 +167,8 @@ public ResponseEntity listCollectorByHostNameAndModul @RequestParam CollectorModuleEnum module) { final String ctx = CLASSNAME + ".listCollectorByHostNameAndModule"; try { - return ResponseEntity.ok().body(collectorService.listCollector( - collectorService.getListRequestByHostnameAndModule(hostname, module))); + return ResponseEntity.ok().body(collectorOpsService.listCollector( + collectorOpsService.getListRequestByHostnameAndModule(hostname, module))); } catch (BadRequestAlertException e) { String msg = ctx + ": " + e.getLocalizedMessage(); log.error(msg); @@ -224,7 +195,7 @@ public ResponseEntity> getModuleGroups(@PathVariable String public ResponseEntity updateGroup(@Valid @RequestBody UtmNetworkScanResource.UpdateGroupRequestBody body) { final String ctx = CLASSNAME + ".updateGroup"; try { - collectorService.updateGroup(body.getAssetsIds(), body.getAssetGroupId()); + collectorOpsService.updateGroup(body.getAssetsIds(), body.getAssetGroupId()); return ResponseEntity.ok().build(); } catch (Exception e) { String msg = ctx + ": " + e.getMessage(); @@ -240,7 +211,7 @@ public ResponseEntity> searchGroupsByFilter(AssetGroupFilter final String ctx = CLASSNAME + ".searchGroupsByFilter"; try { - Page page = collectorService.searchGroupsByFilter(filter, pageable); + Page page = collectorOpsService.searchGroupsByFilter(filter, pageable); HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/utm-asset-groups/searchGroupsByFilter"); return ResponseEntity.ok().headers(headers).body(page.getContent()); } catch (Exception e) { @@ -257,7 +228,7 @@ public ResponseEntity> searchByFilters(@ParameterObject Netwo @ParameterObject Pageable pageable) { final String ctx = CLASSNAME + ".searchByFilters"; try { - collectorService.listCollector(ListRequest.newBuilder() + collectorOpsService.listCollector(ListRequest.newBuilder() .setPageNumber(0) .setPageSize(1000000) .setSortBy("") @@ -279,7 +250,7 @@ public ResponseEntity deleteCollector(@PathVariable Long id) { try { log.debug("REST request to delete UtmCollector : {}", id); - collectorService.deleteCollector(id); + collectorOpsService.deleteCollector(id); return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert("UtmCollector", id.toString())).build(); } catch (Exception e) { applicationEventService.createEvent(e.getMessage(), ApplicationEventType.ERROR); @@ -289,17 +260,17 @@ public ResponseEntity deleteCollector(@PathVariable Long id) { } @PostMapping("/collectors-config") - public ResponseEntity> upsertCollectorsConfig(@RequestBody List collectors) { + public ResponseEntity> upsertCollectorsConfig(@RequestBody List collectors) { Map results = new HashMap<>(); final String ctx = CLASSNAME + ".upsertCollectorsConfig"; CollectorConfig cacheConfig = null; List> collectorsResults = new ArrayList<>(); - for (CollectorConfigKeysDTO collectorConfig : collectors) { + for (CollectorConfigDTO collectorConfig : collectors) { Map collectorResult = new HashMap<>(); collectorResult.put("collectorId", collectorConfig.getCollector().getId()); try { - cacheConfig = this.collectorService.cacheCurrentCollectorConfig(collectorConfig.getCollector()); + cacheConfig = this.collectorOpsService.cacheCurrentCollectorConfig(collectorConfig.getCollector()); this.upsert(collectorConfig); collectorResult.put("status", "success"); } catch (Exception e) { @@ -355,12 +326,12 @@ private ResponseEntity logAndResponse(ErrorResponse error) { return ResponseUtil.buildErrorResponse(error.getStatus(), error.getMessage()); } - private void upsert(CollectorConfigKeysDTO collectorConfig) throws Exception { + private void upsert(CollectorConfigDTO collectorConfig) throws Exception { // Update local database with new configuration - this.collectorService.updateCollectorConfigurationKeys(collectorConfig); + this.collectorOpsService.updateCollectorConfigurationKeys(collectorConfig); // Attempt to update collector configuration via gRPC - this.collectorService.updateCollectorConfigViaGrpc(collectorConfig, collectorConfig.getCollector()); + this.collectorOpsService.updateCollectorConfigViaGrpc(collectorConfig, collectorConfig.getCollector()); } } From 3c6e3473a07f458d1cc09201d1effc553e9168f1 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:49 -0600 Subject: [PATCH 060/240] feat: add logging to updateConfigurationKeys method in UtmModuleGroupConfigurationService --- .../UtmModuleGroupConfigurationService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java index d8d00387c..59bde8c84 100644 --- a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java +++ b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java @@ -10,6 +10,7 @@ import com.park.utmstack.util.CipherUtil; import com.park.utmstack.util.exceptions.ApiException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,6 +29,7 @@ @Service @Transactional @RequiredArgsConstructor +@Slf4j public class UtmModuleGroupConfigurationService { private static final String CLASSNAME = "UtmModuleGroupConfigurationService"; @@ -54,7 +56,7 @@ public void createConfigurationKeys(List keys) thro * @param keys List of configuration keys to save * @throws Exception In case of any error */ - public UtmModule updateConfigurationKeys(Long moduleId, List keys) throws Exception { + public UtmModule updateConfigurationKeys(Long moduleId, List keys) { final String ctx = CLASSNAME + ".updateConfigurationKeys"; try { if (CollectionUtils.isEmpty(keys)) @@ -77,7 +79,8 @@ public UtmModule updateConfigurationKeys(Long moduleId, List new ApiException(String.format("Module with ID %1$s not found", moduleId), HttpStatus.NOT_FOUND)); } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); + log.error("{}: Error updating configuration keys: {}", ctx, e.getMessage()); + throw new ApiException(String.format("%s: Error updating configuration keys: %s", ctx, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } } From 064ee09057485d14e8b280a1a772ee8aae7fac02 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 15:22:03 -0600 Subject: [PATCH 061/240] fix(module.service): return full response body instead of filtering AS_400 module Signed-off-by: Manuel Abascal --- frontend/src/app/app-module/services/module.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/app-module/services/module.service.ts b/frontend/src/app/app-module/services/module.service.ts index e6e8da2c3..11f4fe494 100644 --- a/frontend/src/app/app-module/services/module.service.ts +++ b/frontend/src/app/app-module/services/module.service.ts @@ -83,7 +83,7 @@ export class ModuleService { m.prettyName = m.prettyName + ' GravityZone'; } }); - return response.body.filter(m => m.moduleName !== this.utmModulesEnum.AS_400); + return response.body; }), catchError(error => { console.error(error); From cd429abbf0be2ceeb4d26f0b2f3902ff9bdf16ca Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 09:55:36 -0600 Subject: [PATCH 062/240] feat: implement gRPC client and service for collector management # Conflicts: # backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java # backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java # backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java # backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java --- .../config/CollectorConfiguration.java | 25 +++++++++++++++++++ .../utmstack/config/GrpcConfiguration.java | 1 - .../grpc/client/CollectorServiceClient.java | 8 +++++- .../client/PanelCollectorServiceClient.java | 6 ++++- .../GrpcInternalKeyInterceptor.java | 25 +++++++++++++++++++ .../collectors/CollectorOpsService.java | 24 +++++++++++------- 6 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java new file mode 100644 index 000000000..cb37b9f36 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java @@ -0,0 +1,25 @@ +package com.park.utmstack.config; + +import com.park.utmstack.grpc.connection.GrpcConnection; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CollectorConfiguration { + + @Value("${grpc.server.address}") + private String serverAddress; + + @Value("${grpc.server.port}") + private Integer serverPort; + + @Bean + public GrpcConnection collectorConnection() { + + GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); + collectorConnection.connect(); + + return collectorConnection; + } +} diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 9d014b830..93f6d97bd 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -12,7 +12,6 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; -@Configuration public class GrpcConfiguration { private ManagedChannel channel; diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java index faa50bbc6..1760c6005 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -3,6 +3,7 @@ import agent.CollectorOuterClass; import agent.CollectorServiceGrpc; import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import com.park.utmstack.service.grpc.AuthResponse; import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; @@ -26,7 +27,12 @@ public CollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { String ctx = "CollectorServiceClient.listCollectors"; try { - return baseStub.listCollector(request); + + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.listCollector(request); + } catch (StatusRuntimeException e) { log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java index db0e30e76..d1f8cd1f3 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -2,6 +2,7 @@ import agent.CollectorOuterClass; import agent.PanelCollectorServiceGrpc; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; @@ -18,7 +19,10 @@ public PanelCollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { try { - return baseStub.registerCollectorConfig(config); + PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.registerCollectorConfig(config); } catch (StatusRuntimeException e) { throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java new file mode 100644 index 000000000..eb620fd2a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java @@ -0,0 +1,25 @@ +package com.park.utmstack.grpc.interceptor; + +import com.park.utmstack.config.Constants; +import io.grpc.*; + +public class GrpcInternalKeyInterceptor implements ClientInterceptor { + + private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); + + @Override + public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>( + channel.newCall(methodDescriptor, callOptions)) { + + @Override public void start(Listener responseListener, Metadata headers) { + String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); + + headers.put(INTERNAL_KEY_HEADER, internalKey); + headers.put(TYPE_HEADER, "internal"); + super.start(responseListener, headers); + } }; + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index df0ef0452..0d3263a5f 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -7,6 +7,7 @@ import agent.CollectorOuterClass.Collector; import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; +import agent.CollectorOuterClass.ConfigRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -16,19 +17,23 @@ import com.park.utmstack.domain.network_scan.UtmAssetGroup; import com.park.utmstack.grpc.client.CollectorServiceClient; import com.park.utmstack.grpc.client.PanelCollectorServiceClient; +import com.park.utmstack.grpc.connection.GrpcConnection; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.repository.collector.UtmCollectorRepository; +import com.park.utmstack.security.SecurityUtils; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.application_modules.UtmModuleService; import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; import com.park.utmstack.service.dto.collectors.CollectorHostnames; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.grpc.AuthResponse; +import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; @@ -37,6 +42,7 @@ import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; +import com.utmstack.grpc.service.CollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; @@ -64,7 +70,7 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); - private final ManagedChannel channel; + private final GrpcConnection grpcConnection; private final PanelCollectorServiceClient panelCollectorService; private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; @@ -86,7 +92,7 @@ public class CollectorOpsService { private final CollectorValidatorService collectorValidatorService; - public CollectorOpsService(ManagedChannel channel, + public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleGroupService moduleGroupService, UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository, UtmCollectorRepository utmCollectorRepository, @@ -97,9 +103,9 @@ public CollectorOpsService(ManagedChannel channel, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { - this.channel = channel; - this.panelCollectorService = new PanelCollectorServiceClient(channel); - this.collectorService = new CollectorServiceClient(channel); + this.grpcConnection = grpcConnection; + this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); + this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; @@ -457,7 +463,7 @@ public void deleteCollector(Long id) throws Exception { } - public String validateCollectorConfig(CollectorConfigDTO collectorConfig) { + public String validateCollectorConfig(CollectorConfigKeysDTO collectorConfig) { Errors errors = new BeanPropertyBindingResult(collectorConfig, "updateConfigurationKeysBody"); collectorValidatorService.validate(collectorConfig, errors); @@ -472,7 +478,7 @@ public CollectorConfig cacheCurrentCollectorConfig(CollectorDTO collectorDTO) th } public void updateCollectorConfigViaGrpc( - CollectorConfigDTO collectorConfig, + CollectorConfigKeysDTO collectorConfig, CollectorDTO collectorDTO) throws CollectorConfigurationGrpcException { this.upsertCollectorConfig( @@ -480,7 +486,7 @@ public void updateCollectorConfigViaGrpc( this.mapPasswordConfiguration(collectorConfig.getKeys()), collectorDTO)); } - public void updateCollectorConfigurationKeys(CollectorConfigDTO collectorConfig) throws Exception { + public void updateCollectorConfigurationKeys(CollectorConfigKeysDTO collectorConfig) throws Exception { final String ctx = CLASSNAME + ".updateCollectorConfigurationKeys"; try { List configs = utmModuleGroupRepository From 5f060b05a921d1b8641a1d17a2830486bc657b86 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 10:10:53 -0600 Subject: [PATCH 063/240] feat: implement gRPC client and service for collector management --- .../main/java/com/park/utmstack/config/GrpcConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 93f6d97bd..9d014b830 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -12,6 +12,7 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; +@Configuration public class GrpcConfiguration { private ManagedChannel channel; From cde98faaaf034e34361e8a72915f2ae576d9936b Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 11:10:51 -0600 Subject: [PATCH 064/240] feat: implement gRPC client and service for collector management --- .../service/collectors/CollectorOpsService.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 0d3263a5f..78b855cf8 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -7,7 +7,6 @@ import agent.CollectorOuterClass.Collector; import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; -import agent.CollectorOuterClass.ConfigRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -22,7 +21,6 @@ import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.repository.collector.UtmCollectorRepository; -import com.park.utmstack.security.SecurityUtils; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.application_modules.UtmModuleService; import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; @@ -32,8 +30,6 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; -import com.park.utmstack.service.grpc.AuthResponse; -import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; @@ -42,7 +38,6 @@ import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; -import com.utmstack.grpc.service.CollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; @@ -70,7 +65,7 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); - private final GrpcConnection grpcConnection; + private final ManagedChannel channel; private final PanelCollectorServiceClient panelCollectorService; private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; @@ -92,7 +87,7 @@ public class CollectorOpsService { private final CollectorValidatorService collectorValidatorService; - public CollectorOpsService(GrpcConnection grpcConnection, + public CollectorOpsService(ManagedChannel channel, UtmModuleGroupService moduleGroupService, UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository, UtmCollectorRepository utmCollectorRepository, @@ -103,9 +98,9 @@ public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { - this.grpcConnection = grpcConnection; - this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); - this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); + this.channel = channel; + this.panelCollectorService = new PanelCollectorServiceClient(channel); + this.collectorService = new CollectorServiceClient(channel); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; From db0532ccb040e3829b34215671d59d973b80836b Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 12:22:21 -0600 Subject: [PATCH 065/240] feat: remove unused GrpcInternalKeyInterceptor from collector service clients --- .../config/CollectorConfiguration.java | 25 ------------------- .../grpc/client/CollectorServiceClient.java | 8 +----- .../client/PanelCollectorServiceClient.java | 6 +---- .../GrpcInternalKeyInterceptor.java | 25 ------------------- 4 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java delete mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java deleted file mode 100644 index cb37b9f36..000000000 --- a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.config; - -import com.park.utmstack.grpc.connection.GrpcConnection; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class CollectorConfiguration { - - @Value("${grpc.server.address}") - private String serverAddress; - - @Value("${grpc.server.port}") - private Integer serverPort; - - @Bean - public GrpcConnection collectorConnection() { - - GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); - collectorConnection.connect(); - - return collectorConnection; - } -} diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java index 1760c6005..faa50bbc6 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -3,7 +3,6 @@ import agent.CollectorOuterClass; import agent.CollectorServiceGrpc; import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import com.park.utmstack.service.grpc.AuthResponse; import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; @@ -27,12 +26,7 @@ public CollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { String ctx = "CollectorServiceClient.listCollectors"; try { - - CollectorServiceGrpc.CollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.listCollector(request); - + return baseStub.listCollector(request); } catch (StatusRuntimeException e) { log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java index d1f8cd1f3..db0e30e76 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -2,7 +2,6 @@ import agent.CollectorOuterClass; import agent.PanelCollectorServiceGrpc; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; @@ -19,10 +18,7 @@ public PanelCollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { try { - PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.registerCollectorConfig(config); + return baseStub.registerCollectorConfig(config); } catch (StatusRuntimeException e) { throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java deleted file mode 100644 index eb620fd2a..000000000 --- a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.grpc.interceptor; - -import com.park.utmstack.config.Constants; -import io.grpc.*; - -public class GrpcInternalKeyInterceptor implements ClientInterceptor { - - private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); - private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); - - @Override - public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { - - return new ForwardingClientCall.SimpleForwardingClientCall<>( - channel.newCall(methodDescriptor, callOptions)) { - - @Override public void start(Listener responseListener, Metadata headers) { - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - headers.put(INTERNAL_KEY_HEADER, internalKey); - headers.put(TYPE_HEADER, "internal"); - super.start(responseListener, headers); - } }; - } -} From 9a2ce11e87111d205a47725b38f0f774faf80994 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 09:55:36 -0600 Subject: [PATCH 066/240] feat: implement gRPC client and service for collector management --- .../config/CollectorConfiguration.java | 25 +++++++++++++++++++ .../utmstack/config/GrpcConfiguration.java | 1 - .../grpc/client/CollectorServiceClient.java | 8 +++++- .../client/PanelCollectorServiceClient.java | 6 ++++- .../GrpcInternalKeyInterceptor.java | 25 +++++++++++++++++++ .../collectors/CollectorOpsService.java | 15 +++++++---- 6 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java create mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java new file mode 100644 index 000000000..cb37b9f36 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java @@ -0,0 +1,25 @@ +package com.park.utmstack.config; + +import com.park.utmstack.grpc.connection.GrpcConnection; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CollectorConfiguration { + + @Value("${grpc.server.address}") + private String serverAddress; + + @Value("${grpc.server.port}") + private Integer serverPort; + + @Bean + public GrpcConnection collectorConnection() { + + GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); + collectorConnection.connect(); + + return collectorConnection; + } +} diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 9d014b830..93f6d97bd 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -12,7 +12,6 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; -@Configuration public class GrpcConfiguration { private ManagedChannel channel; diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java index faa50bbc6..1760c6005 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -3,6 +3,7 @@ import agent.CollectorOuterClass; import agent.CollectorServiceGrpc; import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import com.park.utmstack.service.grpc.AuthResponse; import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; @@ -26,7 +27,12 @@ public CollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { String ctx = "CollectorServiceClient.listCollectors"; try { - return baseStub.listCollector(request); + + CollectorServiceGrpc.CollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.listCollector(request); + } catch (StatusRuntimeException e) { log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java index db0e30e76..d1f8cd1f3 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -2,6 +2,7 @@ import agent.CollectorOuterClass; import agent.PanelCollectorServiceGrpc; +import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; @@ -18,7 +19,10 @@ public PanelCollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { try { - return baseStub.registerCollectorConfig(config); + PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = + baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); + + return stub.registerCollectorConfig(config); } catch (StatusRuntimeException e) { throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java new file mode 100644 index 000000000..eb620fd2a --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java @@ -0,0 +1,25 @@ +package com.park.utmstack.grpc.interceptor; + +import com.park.utmstack.config.Constants; +import io.grpc.*; + +public class GrpcInternalKeyInterceptor implements ClientInterceptor { + + private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); + private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); + + @Override + public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>( + channel.newCall(methodDescriptor, callOptions)) { + + @Override public void start(Listener responseListener, Metadata headers) { + String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); + + headers.put(INTERNAL_KEY_HEADER, internalKey); + headers.put(TYPE_HEADER, "internal"); + super.start(responseListener, headers); + } }; + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 78b855cf8..0d3263a5f 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -7,6 +7,7 @@ import agent.CollectorOuterClass.Collector; import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; +import agent.CollectorOuterClass.ConfigRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -21,6 +22,7 @@ import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.repository.collector.UtmCollectorRepository; +import com.park.utmstack.security.SecurityUtils; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.application_modules.UtmModuleService; import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; @@ -30,6 +32,8 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.grpc.AuthResponse; +import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; @@ -38,6 +42,7 @@ import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; +import com.utmstack.grpc.service.CollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; @@ -65,7 +70,7 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); - private final ManagedChannel channel; + private final GrpcConnection grpcConnection; private final PanelCollectorServiceClient panelCollectorService; private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; @@ -87,7 +92,7 @@ public class CollectorOpsService { private final CollectorValidatorService collectorValidatorService; - public CollectorOpsService(ManagedChannel channel, + public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleGroupService moduleGroupService, UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository, UtmCollectorRepository utmCollectorRepository, @@ -98,9 +103,9 @@ public CollectorOpsService(ManagedChannel channel, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { - this.channel = channel; - this.panelCollectorService = new PanelCollectorServiceClient(channel); - this.collectorService = new CollectorServiceClient(channel); + this.grpcConnection = grpcConnection; + this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); + this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; From 4ca5c56974a492e39e7be7dac1d57f981884f448 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 10:10:53 -0600 Subject: [PATCH 067/240] feat: implement gRPC client and service for collector management --- .../main/java/com/park/utmstack/config/GrpcConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java index 93f6d97bd..9d014b830 100644 --- a/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/GrpcConfiguration.java @@ -12,6 +12,7 @@ import javax.annotation.PreDestroy; import javax.net.ssl.SSLException; +@Configuration public class GrpcConfiguration { private ManagedChannel channel; From 12e637b5566af3cbd1e608daf5fe91c4bfd5d1f3 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 11:10:51 -0600 Subject: [PATCH 068/240] feat: implement gRPC client and service for collector management --- .../service/collectors/CollectorOpsService.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 0d3263a5f..78b855cf8 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -7,7 +7,6 @@ import agent.CollectorOuterClass.Collector; import agent.CollectorOuterClass.CollectorModule; import agent.CollectorOuterClass.CollectorConfigGroup; -import agent.CollectorOuterClass.ConfigRequest; import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; @@ -22,7 +21,6 @@ import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.repository.collector.UtmCollectorRepository; -import com.park.utmstack.security.SecurityUtils; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.application_modules.UtmModuleService; import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; @@ -32,8 +30,6 @@ import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; -import com.park.utmstack.service.grpc.AuthResponse; -import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.CipherUtil; @@ -42,7 +38,6 @@ import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; import com.utmstack.grpc.exception.CollectorServiceGrpcException; import com.utmstack.grpc.exception.GrpcConnectionException; -import com.utmstack.grpc.service.CollectorService; import io.grpc.*; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.domain.Page; @@ -70,7 +65,7 @@ public class CollectorOpsService { private final String CLASSNAME = "CollectorOpsService"; private final Logger log = LoggerFactory.getLogger(CollectorOpsService.class); - private final GrpcConnection grpcConnection; + private final ManagedChannel channel; private final PanelCollectorServiceClient panelCollectorService; private final CollectorServiceClient collectorService; private final UtmModuleGroupService moduleGroupService; @@ -92,7 +87,7 @@ public class CollectorOpsService { private final CollectorValidatorService collectorValidatorService; - public CollectorOpsService(GrpcConnection grpcConnection, + public CollectorOpsService(ManagedChannel channel, UtmModuleGroupService moduleGroupService, UtmModuleGroupConfigurationRepository utmModuleGroupConfigurationRepository, UtmCollectorRepository utmCollectorRepository, @@ -103,9 +98,9 @@ public CollectorOpsService(GrpcConnection grpcConnection, UtmModuleRepository utmModuleRepository, CollectorValidatorService collectorValidatorService) throws GrpcConnectionException { - this.grpcConnection = grpcConnection; - this.panelCollectorService = new PanelCollectorServiceClient(grpcConnection.getChannel()); - this.collectorService = new CollectorServiceClient(grpcConnection.getChannel()); + this.channel = channel; + this.panelCollectorService = new PanelCollectorServiceClient(channel); + this.collectorService = new CollectorServiceClient(channel); this.moduleGroupService = moduleGroupService; this.utmModuleGroupConfigurationRepository = utmModuleGroupConfigurationRepository; this.utmCollectorRepository = utmCollectorRepository; From 5ca2febe706e279aceaf99c70a6dd2fa18bd93ed Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 28 Jan 2026 12:22:21 -0600 Subject: [PATCH 069/240] feat: remove unused GrpcInternalKeyInterceptor from collector service clients --- .../config/CollectorConfiguration.java | 25 ------------------- .../grpc/client/CollectorServiceClient.java | 8 +----- .../client/PanelCollectorServiceClient.java | 6 +---- .../GrpcInternalKeyInterceptor.java | 25 ------------------- 4 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java delete mode 100644 backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java diff --git a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java b/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java deleted file mode 100644 index cb37b9f36..000000000 --- a/backend/src/main/java/com/park/utmstack/config/CollectorConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.config; - -import com.park.utmstack.grpc.connection.GrpcConnection; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class CollectorConfiguration { - - @Value("${grpc.server.address}") - private String serverAddress; - - @Value("${grpc.server.port}") - private Integer serverPort; - - @Bean - public GrpcConnection collectorConnection() { - - GrpcConnection collectorConnection = new GrpcConnection(this.serverAddress, this.serverPort); - collectorConnection.connect(); - - return collectorConnection; - } -} diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java index 1760c6005..faa50bbc6 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/CollectorServiceClient.java @@ -3,7 +3,6 @@ import agent.CollectorOuterClass; import agent.CollectorServiceGrpc; import com.park.utmstack.grpc.interceptor.CollectorAuthInterceptor; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import com.park.utmstack.service.grpc.AuthResponse; import com.park.utmstack.service.grpc.DeleteRequest; import com.park.utmstack.service.grpc.ListRequest; @@ -27,12 +26,7 @@ public CollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ListCollectorResponse listCollectors(ListRequest request) { String ctx = "CollectorServiceClient.listCollectors"; try { - - CollectorServiceGrpc.CollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.listCollector(request); - + return baseStub.listCollector(request); } catch (StatusRuntimeException e) { log.error("{}: An error occurred while listing collectors: {}", ctx, e.getMessage()); throw new ApiException(String.format("%s: gRPC error listing collectors", ctx), HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java index d1f8cd1f3..db0e30e76 100644 --- a/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java +++ b/backend/src/main/java/com/park/utmstack/grpc/client/PanelCollectorServiceClient.java @@ -2,7 +2,6 @@ import agent.CollectorOuterClass; import agent.PanelCollectorServiceGrpc; -import com.park.utmstack.grpc.interceptor.GrpcInternalKeyInterceptor; import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; @@ -19,10 +18,7 @@ public PanelCollectorServiceClient(ManagedChannel channel) { public CollectorOuterClass.ConfigKnowledge insertCollectorConfig(CollectorOuterClass.CollectorConfig config) { try { - PanelCollectorServiceGrpc.PanelCollectorServiceBlockingStub stub = - baseStub.withInterceptors(new GrpcInternalKeyInterceptor()); - - return stub.registerCollectorConfig(config); + return baseStub.registerCollectorConfig(config); } catch (StatusRuntimeException e) { throw new RuntimeException("gRPC error inserting collector config: " + e.getMessage(), e); diff --git a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java b/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java deleted file mode 100644 index eb620fd2a..000000000 --- a/backend/src/main/java/com/park/utmstack/grpc/interceptor/GrpcInternalKeyInterceptor.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.park.utmstack.grpc.interceptor; - -import com.park.utmstack.config.Constants; -import io.grpc.*; - -public class GrpcInternalKeyInterceptor implements ClientInterceptor { - - private static final Metadata.Key INTERNAL_KEY_HEADER = Metadata.Key.of("internal-key", Metadata.ASCII_STRING_MARSHALLER); - private static final Metadata.Key TYPE_HEADER = Metadata.Key.of("type", Metadata.ASCII_STRING_MARSHALLER); - - @Override - public ClientCall interceptCall(MethodDescriptor methodDescriptor, CallOptions callOptions, Channel channel) { - - return new ForwardingClientCall.SimpleForwardingClientCall<>( - channel.newCall(methodDescriptor, callOptions)) { - - @Override public void start(Listener responseListener, Metadata headers) { - String internalKey = System.getenv(Constants.ENV_INTERNAL_KEY); - - headers.put(INTERNAL_KEY_HEADER, internalKey); - headers.put(TYPE_HEADER, "internal"); - super.start(responseListener, headers); - } }; - } -} From 779ffdd2a0eb756dff28f71729509701633185c3 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 29 Jan 2026 15:18:05 -0600 Subject: [PATCH 070/240] feat: update CollectorConfig validation and add CollectorService for gRPC integration --- .../utmstack/service/collectors/CollectorOpsService.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java index 78b855cf8..df0ef0452 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorOpsService.java @@ -16,7 +16,6 @@ import com.park.utmstack.domain.network_scan.UtmAssetGroup; import com.park.utmstack.grpc.client.CollectorServiceClient; import com.park.utmstack.grpc.client.PanelCollectorServiceClient; -import com.park.utmstack.grpc.connection.GrpcConnection; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; @@ -26,7 +25,7 @@ import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; import com.park.utmstack.service.dto.collectors.CollectorHostnames; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; @@ -458,7 +457,7 @@ public void deleteCollector(Long id) throws Exception { } - public String validateCollectorConfig(CollectorConfigKeysDTO collectorConfig) { + public String validateCollectorConfig(CollectorConfigDTO collectorConfig) { Errors errors = new BeanPropertyBindingResult(collectorConfig, "updateConfigurationKeysBody"); collectorValidatorService.validate(collectorConfig, errors); @@ -473,7 +472,7 @@ public CollectorConfig cacheCurrentCollectorConfig(CollectorDTO collectorDTO) th } public void updateCollectorConfigViaGrpc( - CollectorConfigKeysDTO collectorConfig, + CollectorConfigDTO collectorConfig, CollectorDTO collectorDTO) throws CollectorConfigurationGrpcException { this.upsertCollectorConfig( @@ -481,7 +480,7 @@ public void updateCollectorConfigViaGrpc( this.mapPasswordConfiguration(collectorConfig.getKeys()), collectorDTO)); } - public void updateCollectorConfigurationKeys(CollectorConfigKeysDTO collectorConfig) throws Exception { + public void updateCollectorConfigurationKeys(CollectorConfigDTO collectorConfig) throws Exception { final String ctx = CLASSNAME + ".updateCollectorConfigurationKeys"; try { List configs = utmModuleGroupRepository From e0422ef1677e98e3f47972afcc84ce0e9e9e679b Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 30 Jan 2026 12:45:46 -0600 Subject: [PATCH 071/240] refactor(collector): simplify DTOs and enhance service methods for listing collectors --- .../service/collectors/CollectorService.java | 97 ++++++++++++++++++- .../dto/collectors/dto/CollectorDTO.java | 92 +----------------- .../dto/ListCollectorsResponseDTO.java | 20 +--- .../rest/collectors/UtmCollectorResource.java | 77 +++++---------- 4 files changed, 128 insertions(+), 158 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java index bbffd535a..beecb7759 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java @@ -1,28 +1,119 @@ package com.park.utmstack.service.collectors; import agent.CollectorOuterClass; -import com.park.utmstack.service.application_events.ApplicationEventService; +import com.park.utmstack.config.Constants; +import com.park.utmstack.domain.collector.UtmCollector; import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; +import com.park.utmstack.service.dto.collectors.CollectorHostnames; +import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; +import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; +import com.park.utmstack.service.grpc.ListRequest; +import com.park.utmstack.util.exceptions.ApiException; +import com.park.utmstack.web.rest.errors.BadRequestAlertException; +import com.utmstack.grpc.exception.CollectorServiceGrpcException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.stream.Collectors; + +import static com.park.utmstack.config.RestTemplateConfiguration.CLASSNAME; @Service +@Slf4j @RequiredArgsConstructor public class CollectorService { private final CollectorGrpcService collectorGrpcService; - private final ApplicationEventService eventService; private final UtmModuleGroupConfigurationService moduleGroupConfigurationService; private final CollectorConfigBuilder CollectorConfigBuilder; + private final UtmCollectorService utmCollectorService; public void upsertCollectorConfig(CollectorConfigDTO collectorConfig) { - this.moduleGroupConfigurationService.updateConfigurationKeys(collectorConfig.getModuleId(), collectorConfig.getKeys()); + this.moduleGroupConfigurationService.updateConfigurationKeys(collectorConfig.getModuleId(), collectorConfig.getKeys()); CollectorOuterClass.CollectorConfig collector = CollectorConfigBuilder.build(collectorConfig); collectorGrpcService.upsertCollectorConfig(collector); } + public ListCollectorsResponseDTO listCollector(ListRequest request) { + return this.getListCollector(request); + } + + public ListCollectorsResponseDTO listCollector(String hostname, CollectorModuleEnum module) { + + String query = ""; + if (module != null && StringUtils.hasText(hostname)) { + query = "module.Is=" + module.name() + "&hostname.Is=" + hostname; + } else if (StringUtils.hasText(hostname)) { + query = "hostname.Is=" + hostname; + } else if (module != null) { + query = "module.Is=" + module.name(); + } + + var request = ListRequest.newBuilder() + .setPageNumber(1) + .setPageSize(1000000) + .setSearchQuery(query) + .setSortBy("id,desc") + .build(); + + CollectorOuterClass.ListCollectorResponse collectorResponse = collectorGrpcService.listCollectors(request); + return mapToListCollectorsResponseDTO(collectorResponse); + } + + public CollectorHostnames listCollectorHostnames(ListRequest request) { + + + CollectorOuterClass.ListCollectorResponse response = collectorGrpcService.listCollectors(request); + CollectorHostnames collectorHostnames = new CollectorHostnames(); + + response.getRowsList().forEach(c -> { + collectorHostnames.getHostname().add(c.getHostname()); + }); + + return collectorHostnames; + + } + + private ListCollectorsResponseDTO getListCollector(ListRequest request) { + + CollectorOuterClass.ListCollectorResponse collectorResponse = collectorGrpcService.listCollectors(request); + return mapToListCollectorsResponseDTO(collectorResponse); + } + + + private ListCollectorsResponseDTO mapToListCollectorsResponseDTO(CollectorOuterClass.ListCollectorResponse response) { + final String ctx = CLASSNAME + ".mapToListCollectorsResponseDTO"; + try { + ListCollectorsResponseDTO dto = new ListCollectorsResponseDTO(); + + List collectorDTOS = response.getRowsList().stream() + .map(this::protoToCollectorDto) + .collect(Collectors.toList()); + + this.utmCollectorService.synchronize(collectorDTOS); + + dto.setCollectors(collectorDTOS); + dto.setTotal(response.getTotal()); + + return dto; + } catch (Exception e) { + log.error("{}: Error mapping ListCollectorResponse to ListCollectorsResponseDTO: {}", ctx, e.getMessage()); + throw new ApiException(String.format("%s: Error mapping ListCollectorResponse to ListCollectorsResponseDTO: %s", ctx, e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + private CollectorDTO protoToCollectorDto(CollectorOuterClass.Collector collector) { + UtmCollector utmCollector = this.utmCollectorService.saveCollector(collector); + return new CollectorDTO(utmCollector); + } + } diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorDTO.java index 703656752..3dbb43e3f 100644 --- a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorDTO.java +++ b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorDTO.java @@ -1,12 +1,15 @@ package com.park.utmstack.service.dto.collectors.dto; -import agent.CollectorOuterClass.Collector; import com.park.utmstack.domain.collector.UtmCollector; import com.park.utmstack.domain.network_scan.UtmAssetGroup; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; import com.park.utmstack.service.dto.collectors.CollectorStatusEnum; +import lombok.Getter; +import lombok.Setter; +@Setter +@Getter public class CollectorDTO { private int id; private CollectorStatusEnum status; @@ -39,91 +42,4 @@ public CollectorDTO(UtmCollector collector) { this.active = collector.isActive(); } - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public CollectorStatusEnum getStatus() { - return status; - } - - public void setStatus(CollectorStatusEnum status) { - this.status = status; - } - - public String getCollectorKey() { - return collectorKey; - } - - public void setCollectorKey(String collectorKey) { - this.collectorKey = collectorKey; - } - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public String getHostname() { - return hostname; - } - - public void setHostname(String hostname) { - this.hostname = hostname; - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public CollectorModuleEnum getModule() { - return module; - } - - public void setModule(CollectorModuleEnum module) { - this.module = module; - } - - public String getLastSeen() { - return lastSeen; - } - - public void setLastSeen(String lastSeen) { - this.lastSeen = lastSeen; - } - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public UtmAssetGroup getGroup() { - return group; - } - - public void setGroup(UtmAssetGroup group) { - this.group = group; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } } diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/ListCollectorsResponseDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/ListCollectorsResponseDTO.java index 4130476a7..d38813f75 100644 --- a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/ListCollectorsResponseDTO.java +++ b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/ListCollectorsResponseDTO.java @@ -1,26 +1,14 @@ package com.park.utmstack.service.dto.collectors.dto; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; +import lombok.Getter; +import lombok.Setter; import java.util.List; +@Setter +@Getter public class ListCollectorsResponseDTO { private List collectors; private int total; - - public List getCollectors() { - return collectors; - } - - public void setCollectors(List collectors) { - this.collectors = collectors; - } - - public int getTotal() { - return total; - } - - public void setTotal(int total) { - this.total = total; - } } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index f6dfc8303..43cd5891f 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -96,25 +96,18 @@ public ResponseEntity listCollectorsByModule(@Request @RequestParam(required = false) Integer pageSize, @RequestParam(required = false) CollectorModuleEnum module, @RequestParam(required = false) String sortBy) { - final String ctx = CLASSNAME + ".listCollectorsByModule"; - try { - ListRequest request = ListRequest.newBuilder() - .setPageNumber(pageNumber != null ? pageNumber : 0) - .setPageSize(pageSize != null ? pageSize : 1000000) - .setSearchQuery(module != null ? "module.Is=" + module.name() : "") - .setSortBy(sortBy != null ? sortBy : "") - .build(); - - ListCollectorsResponseDTO response = collectorOpsService.listCollector(request); - HttpHeaders headers = new HttpHeaders(); - headers.add("X-Total-Count", Long.toString(response.getTotal())); - return ResponseEntity.ok().headers(headers).body(response); - } catch (BadRequestAlertException e) { - String msg = ctx + ": " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } + + ListRequest request = ListRequest.newBuilder() + .setPageNumber(pageNumber != null ? pageNumber : 0) + .setPageSize(pageSize != null ? pageSize : 1000000) + .setSearchQuery(module != null ? "module.Is=" + module.name() : "") + .setSortBy(sortBy != null ? sortBy : "") + .build(); + + ListCollectorsResponseDTO response = collectorService.listCollector(request); + HttpHeaders headers = new HttpHeaders(); + headers.add("X-Total-Count", Long.toString(response.getTotal())); + return ResponseEntity.ok().headers(headers).body(response); } /** @@ -132,26 +125,16 @@ public ResponseEntity listCollectorHostNames(@RequestParam(r @RequestParam(required = false) Integer pageSize, @RequestParam(required = false) CollectorModuleEnum module, @RequestParam(required = false) String sortBy) { - final String ctx = CLASSNAME + ".listCollectorHostNames"; - try { - ListRequest request = ListRequest.newBuilder() - .setPageNumber(pageNumber != null ? pageNumber : 0) - .setPageSize(pageSize != null ? pageSize : 1000000) - .setSearchQuery(module != null ? "module.Is=" + module : "") - .setSortBy(sortBy != null ? sortBy : "") - .build(); - return ResponseEntity.ok().body(collectorOpsService.listCollectorHostnames(request)); - } catch (BadRequestAlertException e) { - String msg = ctx + ": " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } catch (CollectorServiceGrpcException e) { - String msg = ctx + ": UtmCollector manager is not available or the parameters are wrong, please check." + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_GATEWAY, msg); - } + + ListRequest request = ListRequest.newBuilder() + .setPageNumber(pageNumber != null ? pageNumber : 0) + .setPageSize(pageSize != null ? pageSize : 1000000) + .setSearchQuery(module != null ? "module.Is=" + module : "") + .setSortBy(sortBy != null ? sortBy : "") + .build(); + + return ResponseEntity.ok().body(collectorService.listCollectorHostnames(request)); + } /** @@ -165,16 +148,8 @@ public ResponseEntity listCollectorHostNames(@RequestParam(r @GetMapping("/collector-by-hostname-and-module") public ResponseEntity listCollectorByHostNameAndModule(@RequestParam String hostname, @RequestParam CollectorModuleEnum module) { - final String ctx = CLASSNAME + ".listCollectorByHostNameAndModule"; - try { - return ResponseEntity.ok().body(collectorOpsService.listCollector( - collectorOpsService.getListRequestByHostnameAndModule(hostname, module))); - } catch (BadRequestAlertException e) { - String msg = ctx + ": " + e.getLocalizedMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } + + return ResponseEntity.ok().body(collectorService.listCollector(hostname, module)); } @GetMapping("/groups-by-collectors/{collectorId}") @@ -191,7 +166,7 @@ public ResponseEntity> getModuleGroups(@PathVariable String } } - @PutMapping("/updateGroup") + /*@PutMapping("/updateGroup") public ResponseEntity updateGroup(@Valid @RequestBody UtmNetworkScanResource.UpdateGroupRequestBody body) { final String ctx = CLASSNAME + ".updateGroup"; try { @@ -204,7 +179,7 @@ public ResponseEntity updateGroup(@Valid @RequestBody UtmNetworkScanResour return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( HeaderUtil.createFailureAlert("", "", msg)).body(null); } - } + }*/ @GetMapping("/searchGroupsByFilter") public ResponseEntity> searchGroupsByFilter(AssetGroupFilter filter, Pageable pageable) { From c9afc01b4ffd11253a852d677607abc7562eda5f Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 13 Feb 2026 14:00:58 -0600 Subject: [PATCH 072/240] feat(collector): add methods for deleting collectors and updating groups --- .../UtmModuleGroupService.java | 57 +++++++++++++++--- .../service/collectors/CollectorService.java | 58 ++++++++++++++++++- .../collectors/UtmCollectorService.java | 22 +++++++ .../dto/network_scan/UpdateGroupDTO.java | 18 ++++++ .../network_scan/UpdateTypeRequestBody.java | 18 ++++++ .../rest/collectors/UtmCollectorResource.java | 39 +++++-------- 6 files changed, 178 insertions(+), 34 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/service/dto/network_scan/UpdateGroupDTO.java create mode 100644 backend/src/main/java/com/park/utmstack/service/dto/network_scan/UpdateTypeRequestBody.java diff --git a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java index 117c64155..5bbefdb90 100644 --- a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java +++ b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java @@ -5,7 +5,9 @@ import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; import com.park.utmstack.repository.UtmModuleGroupRepository; +import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.service.application_events.ApplicationEventService; +import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +33,7 @@ public class UtmModuleGroupService { private final UtmModuleGroupRepository moduleGroupRepository; private final UtmModuleService moduleService; private final ApplicationEventService applicationEventService; + private final UtmModuleRepository moduleRepository; /** @@ -126,13 +129,8 @@ public List findAllByModuleId(Long moduleId) throws Exception { } } - public List findAllByCollectorId(String collectorId) throws Exception { - final String ctx = CLASSNAME + ".findAllByModuleName"; - try { - return moduleGroupRepository.findAllByCollector(collectorId); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } + public List findAllByCollectorId(String collectorId) { + return moduleGroupRepository.findAllByCollector(collectorId); } public List findAllWithCollector() throws Exception { @@ -143,4 +141,49 @@ public List findAllWithCollector() throws Exception { throw new Exception(ctx + ": " + e.getMessage()); } } + + @Transactional + public void deleteCollectorById(Long collectorId) { + + List groups = moduleGroupRepository.findAllByCollector(collectorId.toString()); + + if (groups.isEmpty()) { + return; + } + + UtmModuleGroup group = groups.get(0); + + if (group != null) { + handleModuleDeactivationIfNeeded(group, collectorId); + } + + moduleGroupRepository.deleteAllByCollector(collectorId.toString()); + } + + + private void handleModuleDeactivationIfNeeded(UtmModuleGroup group, Long collectorId) { + + UtmModule module = moduleRepository.findById(group.getModuleId()) + .orElseThrow(() -> new IllegalStateException("Module not found")); + + if (!module.getModuleActive()) { + return; + } + + boolean otherCollectorsExist = + moduleGroupRepository.findAllByModuleId(module.getId()) + .stream() + .anyMatch(m -> !m.getCollector().equals(collectorId.toString())); + + if (!otherCollectorsExist) { + moduleService.activateDeactivate( + ModuleActivationDTO.builder() + .serverId(module.getServerId()) + .moduleName(module.getModuleName()) + .activationStatus(false) + .build() + ); + } + } + } diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java index beecb7759..5c0c427b1 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java @@ -2,8 +2,13 @@ import agent.CollectorOuterClass; import com.park.utmstack.config.Constants; +import com.park.utmstack.domain.application_modules.UtmModule; +import com.park.utmstack.domain.application_modules.UtmModuleGroup; import com.park.utmstack.domain.collector.UtmCollector; +import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; +import com.park.utmstack.service.application_modules.UtmModuleGroupService; +import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; import com.park.utmstack.service.dto.collectors.CollectorHostnames; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; @@ -17,9 +22,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import static com.park.utmstack.config.RestTemplateConfiguration.CLASSNAME; @@ -30,6 +38,7 @@ public class CollectorService { private final CollectorGrpcService collectorGrpcService; + private final UtmModuleGroupService moduleGroupService; private final UtmModuleGroupConfigurationService moduleGroupConfigurationService; private final CollectorConfigBuilder CollectorConfigBuilder; private final UtmCollectorService utmCollectorService; @@ -59,7 +68,7 @@ public ListCollectorsResponseDTO listCollector(String hostname, CollectorModuleE var request = ListRequest.newBuilder() .setPageNumber(1) - .setPageSize(1000000) + .setPageSize(10000) .setSearchQuery(query) .setSortBy("id,desc") .build(); @@ -68,6 +77,17 @@ public ListCollectorsResponseDTO listCollector(String hostname, CollectorModuleE return mapToListCollectorsResponseDTO(collectorResponse); } + public Optional findCollectorByHostname(String hostname, CollectorModuleEnum module) { + + ListCollectorsResponseDTO response = this.listCollector(hostname, module); + + if (response.getCollectors() != null && !response.getCollectors().isEmpty()) { + return Optional.of(response.getCollectors().get(0)); + } else { + return Optional.empty(); + } + } + public CollectorHostnames listCollectorHostnames(ListRequest request) { @@ -82,6 +102,42 @@ public CollectorHostnames listCollectorHostnames(ListRequest request) { } + public void deleteCollector(Long id) { + + String ctx = CLASSNAME + ".deleteCollector"; + + Optional collector = utmCollectorService.findById(id); + + if (collector.isEmpty()) { + + log.error("{}: Collector with id {} not found", ctx, id); + throw new ApiException(String.format("%s: Collector with id %d not found", ctx, id), HttpStatus.NOT_FOUND); + + } else if (collector.get().isActive()) { + + var collectorToDelete = collector.get(); + + Optional collectorDTO = this.findCollectorByHostname( + collector.get().getHostname(), + CollectorModuleEnum.valueOf(collectorToDelete.getModule())); + + if (collectorDTO.isEmpty()) { + + log.error("{}: Collector with id {} not found in Agent Manager", ctx, id); + throw new ApiException(String.format("%s: Collector with id %d not found in Agent Manager", ctx, id), HttpStatus.NOT_FOUND); + + } else { + var c = collectorDTO.get(); + collectorGrpcService.deleteCollector(c.getId(), c.getCollectorKey()); + } + + this.moduleGroupService.deleteCollectorById(collectorToDelete.getId()); + + } + + this.utmCollectorService.deleteCollector(id); + } + private ListCollectorsResponseDTO getListCollector(ListRequest request) { CollectorOuterClass.ListCollectorResponse collectorResponse = collectorGrpcService.listCollectors(request); diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/UtmCollectorService.java b/backend/src/main/java/com/park/utmstack/service/collectors/UtmCollectorService.java index b7be4f3ca..7f7715c90 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/UtmCollectorService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/UtmCollectorService.java @@ -1,9 +1,12 @@ package com.park.utmstack.service.collectors; import agent.CollectorOuterClass; +import com.park.utmstack.domain.application_modules.UtmModule; +import com.park.utmstack.domain.application_modules.UtmModuleGroup; import com.park.utmstack.domain.collector.UtmCollector; import com.park.utmstack.domain.network_scan.NetworkScanFilter; import com.park.utmstack.repository.collector.UtmCollectorRepository; +import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,11 +14,16 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Objects; +import java.util.Optional; + +import static com.park.utmstack.config.RestTemplateConfiguration.CLASSNAME; @Service public class UtmCollectorService { @@ -96,4 +104,18 @@ private Page filter(NetworkScanFilter f, Pageable p) throws Except throw new Exception(ctx + ": " + e.getMessage()); } } + + @Transactional + public void updateGroup(List collectorsIds, Long assetGroupId) { + utmCollectorRepository.updateGroup(collectorsIds, assetGroupId); + } + + Optional findById(Long id) { + return utmCollectorRepository.findById(id); + } + + @Transactional + public void deleteCollector(Long id) { + utmCollectorRepository.deleteById(id); + } } diff --git a/backend/src/main/java/com/park/utmstack/service/dto/network_scan/UpdateGroupDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/network_scan/UpdateGroupDTO.java new file mode 100644 index 000000000..32015f109 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/dto/network_scan/UpdateGroupDTO.java @@ -0,0 +1,18 @@ +package com.park.utmstack.service.dto.network_scan; + +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Data +public class UpdateGroupDTO { + + @NotEmpty(message = "assetsIds cannot be empty") + private List assetsIds; + + @NotNull(message = "assetGroupId is required") + private Long assetGroupId; +} + diff --git a/backend/src/main/java/com/park/utmstack/service/dto/network_scan/UpdateTypeRequestBody.java b/backend/src/main/java/com/park/utmstack/service/dto/network_scan/UpdateTypeRequestBody.java new file mode 100644 index 000000000..c22ecd393 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/dto/network_scan/UpdateTypeRequestBody.java @@ -0,0 +1,18 @@ +package com.park.utmstack.service.dto.network_scan; + +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotEmpty; +import java.util.List; + +@Data +@Getter +@Setter +public class UpdateTypeRequestBody { + @NotEmpty + private List assetsIds; + + private Long assetTypeId; +} \ No newline at end of file diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index 43cd5891f..2aed09969 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -19,11 +19,11 @@ import com.park.utmstack.service.dto.collectors.dto.ErrorResponse; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; +import com.park.utmstack.service.dto.network_scan.UpdateGroupDTO; import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.util.ResponseUtil; import com.park.utmstack.web.rest.errors.BadRequestAlertException; import com.park.utmstack.web.rest.errors.InternalServerErrorException; -import com.park.utmstack.web.rest.network_scan.UtmNetworkScanResource; import com.park.utmstack.web.rest.util.HeaderUtil; import com.park.utmstack.web.rest.util.PaginationUtil; import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; @@ -154,32 +154,19 @@ public ResponseEntity listCollectorByHostNameAndModul @GetMapping("/groups-by-collectors/{collectorId}") public ResponseEntity> getModuleGroups(@PathVariable String collectorId) { - final String ctx = CLASSNAME + ".getModuleGroups"; - try { - return ResponseEntity.ok(moduleGroupService.findAllByCollectorId(collectorId)); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - log.error(msg); - eventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( - HeaderUtil.createFailureAlert("", "", msg)).body(null); - } + + return ResponseEntity.ok(moduleGroupService.findAllByCollectorId(collectorId)); + + } + + @PutMapping("/updateGroup") + public ResponseEntity updateGroup(@Valid @RequestBody UpdateGroupDTO body) { + + utmCollectorService.updateGroup(body.getAssetsIds(), body.getAssetGroupId()); + + return ResponseEntity.ok().build(); } - /*@PutMapping("/updateGroup") - public ResponseEntity updateGroup(@Valid @RequestBody UtmNetworkScanResource.UpdateGroupRequestBody body) { - final String ctx = CLASSNAME + ".updateGroup"; - try { - collectorOpsService.updateGroup(body.getAssetsIds(), body.getAssetGroupId()); - return ResponseEntity.ok().build(); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - log.error(msg); - eventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( - HeaderUtil.createFailureAlert("", "", msg)).body(null); - } - }*/ @GetMapping("/searchGroupsByFilter") public ResponseEntity> searchGroupsByFilter(AssetGroupFilter filter, Pageable pageable) { @@ -225,7 +212,7 @@ public ResponseEntity deleteCollector(@PathVariable Long id) { try { log.debug("REST request to delete UtmCollector : {}", id); - collectorOpsService.deleteCollector(id); + collectorService.deleteCollector(id); return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert("UtmCollector", id.toString())).build(); } catch (Exception e) { applicationEventService.createEvent(e.getMessage(), ApplicationEventType.ERROR); From 706da693a05119c4a58bcfc6d87ccf10d665c99d Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 13 Feb 2026 15:43:29 -0600 Subject: [PATCH 073/240] feat(collector): add bulk upsert functionality for collector configurations --- .../UtmModuleGroupService.java | 10 +++- .../BulkCollectorConfigResponseDTO.java | 13 +++++ .../collectors/CollectorConfigResultDTO.java | 13 +++++ .../service/collectors/CollectorService.java | 39 ++++++++++++-- .../rest/collectors/UtmCollectorResource.java | 54 ++++--------------- 5 files changed, 81 insertions(+), 48 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/BulkCollectorConfigResponseDTO.java create mode 100644 backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigResultDTO.java diff --git a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java index 5bbefdb90..0e71ac3b0 100644 --- a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java +++ b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java @@ -1,23 +1,31 @@ package com.park.utmstack.service.application_modules; import com.park.utmstack.aop.logging.Loggable; +import com.park.utmstack.config.Constants; import com.park.utmstack.domain.application_events.enums.ApplicationEventType; import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; +import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.service.application_events.ApplicationEventService; import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; +import com.park.utmstack.util.CipherUtil; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import javax.persistence.EntityNotFoundException; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; /** * Service Implementation for managing UtmConfigurationGroup. @@ -34,6 +42,7 @@ public class UtmModuleGroupService { private final UtmModuleService moduleService; private final ApplicationEventService applicationEventService; private final UtmModuleRepository moduleRepository; + private final UtmModuleGroupConfigurationRepository moduleGroupConfigurationRepository; /** @@ -160,7 +169,6 @@ public void deleteCollectorById(Long collectorId) { moduleGroupRepository.deleteAllByCollector(collectorId.toString()); } - private void handleModuleDeactivationIfNeeded(UtmModuleGroup group, Long collectorId) { UtmModule module = moduleRepository.findById(group.getModuleId()) diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/BulkCollectorConfigResponseDTO.java b/backend/src/main/java/com/park/utmstack/service/collectors/BulkCollectorConfigResponseDTO.java new file mode 100644 index 000000000..13158bf5b --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/BulkCollectorConfigResponseDTO.java @@ -0,0 +1,13 @@ +package com.park.utmstack.service.collectors; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class BulkCollectorConfigResponseDTO { + private List results; +} + diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigResultDTO.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigResultDTO.java new file mode 100644 index 000000000..3b59756c0 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorConfigResultDTO.java @@ -0,0 +1,13 @@ +package com.park.utmstack.service.collectors; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class CollectorConfigResultDTO { + private int collectorId; + private boolean success; + private String errorMessage; +} + diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java index 5c0c427b1..b5b7cd386 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java @@ -13,6 +13,7 @@ import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; +import com.park.utmstack.service.dto.collectors.dto.ErrorResponse; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.grpc.ListRequest; import com.park.utmstack.util.exceptions.ApiException; @@ -26,8 +27,7 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import static com.park.utmstack.config.RestTemplateConfiguration.CLASSNAME; @@ -51,6 +51,18 @@ public void upsertCollectorConfig(CollectorConfigDTO collectorConfig) { collectorGrpcService.upsertCollectorConfig(collector); } + public BulkCollectorConfigResponseDTO upsertCollectorsConfig(List collectors) { + + List results = collectors.stream() + .map(this::processSingleCollectorConfig) + .toList(); + + return BulkCollectorConfigResponseDTO.builder() + .results(results) + .build(); + } + + public ListCollectorsResponseDTO listCollector(ListRequest request) { return this.getListCollector(request); } @@ -128,7 +140,7 @@ public void deleteCollector(Long id) { } else { var c = collectorDTO.get(); - collectorGrpcService.deleteCollector(c.getId(), c.getCollectorKey()); + collectorGrpcService.deleteCollector(c.getId(), c.getCollectorKey()); } this.moduleGroupService.deleteCollectorById(collectorToDelete.getId()); @@ -171,5 +183,26 @@ private CollectorDTO protoToCollectorDto(CollectorOuterClass.Collector collector return new CollectorDTO(utmCollector); } + private CollectorConfigResultDTO processSingleCollectorConfig(CollectorConfigDTO dto) { + + try { + this.upsertCollectorConfig(dto); + + return CollectorConfigResultDTO.builder() + .collectorId(dto.getCollector().getId()) + .success(true) + .build(); + + } catch (Exception e) { + + return CollectorConfigResultDTO.builder() + .collectorId(dto.getCollector().getId()) + .success(false) + .errorMessage(e.getMessage()) + .build(); + } + } + + } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index 2aed09969..737c184c9 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -8,6 +8,7 @@ import com.park.utmstack.service.application_events.ApplicationEventService; import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; import com.park.utmstack.service.application_modules.UtmModuleGroupService; +import com.park.utmstack.service.collectors.BulkCollectorConfigResponseDTO; import com.park.utmstack.service.collectors.CollectorOpsService; import com.park.utmstack.service.collectors.CollectorService; import com.park.utmstack.service.collectors.UtmCollectorService; @@ -81,6 +82,13 @@ public ResponseEntity upsertCollectorConfig(@Valid @RequestBody CollectorC return ResponseEntity.noContent().build(); } + @PostMapping("/collectors-config") + public ResponseEntity upsertCollectorsConfig(@RequestBody List collectors) { + + return ResponseEntity.status(HttpStatus.MULTI_STATUS) + .body(collectorService.upsertCollectorsConfig(collectors)); + } + /** * {@code GET /collectors-list} : Get all collectors list by module. * @@ -209,42 +217,8 @@ public ResponseEntity> searchByFilters(@ParameterObject Netwo @DeleteMapping("/collectors/{id}") public ResponseEntity deleteCollector(@PathVariable Long id) { - - try { - log.debug("REST request to delete UtmCollector : {}", id); - collectorService.deleteCollector(id); - return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert("UtmCollector", id.toString())).build(); - } catch (Exception e) { - applicationEventService.createEvent(e.getMessage(), ApplicationEventType.ERROR); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( - HeaderUtil.createFailureAlert("UtmCollector", null, e.getMessage())).body(null); - } - } - - @PostMapping("/collectors-config") - public ResponseEntity> upsertCollectorsConfig(@RequestBody List collectors) { - Map results = new HashMap<>(); - final String ctx = CLASSNAME + ".upsertCollectorsConfig"; - CollectorConfig cacheConfig = null; - - List> collectorsResults = new ArrayList<>(); - for (CollectorConfigDTO collectorConfig : collectors) { - Map collectorResult = new HashMap<>(); - collectorResult.put("collectorId", collectorConfig.getCollector().getId()); - try { - cacheConfig = this.collectorOpsService.cacheCurrentCollectorConfig(collectorConfig.getCollector()); - this.upsert(collectorConfig); - collectorResult.put("status", "success"); - } catch (Exception e) { - ErrorResponse error = this.getError(e, cacheConfig); - collectorResult.put("status", "failure"); - collectorResult.put("errorMessage", error.getMessage()); - } - collectorsResults.add(collectorResult); - } - - results.put("results", collectorsResults); - return ResponseEntity.status(HttpStatus.MULTI_STATUS).body(results); + collectorService.deleteCollector(id); + return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert("UtmCollector", id.toString())).build(); } private ResponseEntity handleUpdateError(Exception e, CollectorConfig cacheConfig, CollectorDTO collectorDTO) { @@ -288,12 +262,4 @@ private ResponseEntity logAndResponse(ErrorResponse error) { return ResponseUtil.buildErrorResponse(error.getStatus(), error.getMessage()); } - private void upsert(CollectorConfigDTO collectorConfig) throws Exception { - - // Update local database with new configuration - this.collectorOpsService.updateCollectorConfigurationKeys(collectorConfig); - - // Attempt to update collector configuration via gRPC - this.collectorOpsService.updateCollectorConfigViaGrpc(collectorConfig, collectorConfig.getCollector()); - } } From 8134afde48d283c1b9be863f87243fabd2a23886 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 13 Feb 2026 16:00:28 -0600 Subject: [PATCH 074/240] refactor(collector): remove unused imports and simplify error handling in UtmCollectorResource --- .../rest/collectors/UtmCollectorResource.java | 65 ++----------------- 1 file changed, 4 insertions(+), 61 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index 737c184c9..20cf7d908 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -1,6 +1,5 @@ package com.park.utmstack.web.rest.collectors; -import agent.CollectorOuterClass.CollectorConfig; import com.park.utmstack.domain.application_events.enums.ApplicationEventType; import com.park.utmstack.domain.application_modules.UtmModuleGroup; import com.park.utmstack.domain.network_scan.AssetGroupFilter; @@ -17,18 +16,12 @@ import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; -import com.park.utmstack.service.dto.collectors.dto.ErrorResponse; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; import com.park.utmstack.service.dto.network_scan.UpdateGroupDTO; import com.park.utmstack.service.grpc.ListRequest; -import com.park.utmstack.util.ResponseUtil; -import com.park.utmstack.web.rest.errors.BadRequestAlertException; -import com.park.utmstack.web.rest.errors.InternalServerErrorException; import com.park.utmstack.web.rest.util.HeaderUtil; import com.park.utmstack.web.rest.util.PaginationUtil; -import com.utmstack.grpc.exception.CollectorConfigurationGrpcException; -import com.utmstack.grpc.exception.CollectorServiceGrpcException; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,10 +34,7 @@ import org.springframework.web.bind.annotation.*; import javax.validation.Valid; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** @@ -196,23 +186,17 @@ public ResponseEntity> searchGroupsByFilter(AssetGroupFilter @GetMapping("/search-by-filters") public ResponseEntity> searchByFilters(@ParameterObject NetworkScanFilter filters, @ParameterObject Pageable pageable) { - final String ctx = CLASSNAME + ".searchByFilters"; - try { + collectorOpsService.listCollector(ListRequest.newBuilder() .setPageNumber(0) - .setPageSize(1000000) + .setPageSize(10000) .setSortBy("") .build()); + Page page = this.utmCollectorService.searchByFilters(filters, pageable); HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/search-by-filters"); return ResponseEntity.ok().headers(headers).body(page.getContent()); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - log.error(msg); - eventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( - HeaderUtil.createFailureAlert("", "", msg)).body(null); - } + } @DeleteMapping("/collectors/{id}") @@ -221,45 +205,4 @@ public ResponseEntity deleteCollector(@PathVariable Long id) { return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert("UtmCollector", id.toString())).build(); } - private ResponseEntity handleUpdateError(Exception e, CollectorConfig cacheConfig, CollectorDTO collectorDTO) { - return logAndResponse(this.getError(e, cacheConfig)); - } - - private ErrorResponse getError(Exception e, CollectorConfig cacheConfig) { - String msg; - HttpStatus status; - - try { - if (e instanceof InternalServerErrorException) { - /*collectorService.upsertCollectorConfig(cacheConfig);*/ - msg = "The collector configuration couldn't be persisted on database: " + e.getLocalizedMessage(); - status = HttpStatus.INTERNAL_SERVER_ERROR; - - } else if (e instanceof CollectorConfigurationGrpcException || e instanceof CollectorServiceGrpcException) { - msg = "UtmCollector manager is not available or the configuration is wrong: " + e.getLocalizedMessage(); - status = HttpStatus.BAD_GATEWAY; - - } else if (e instanceof BadRequestAlertException) { - msg = e.getLocalizedMessage(); - status = HttpStatus.BAD_REQUEST; - - } else { - msg = "Unexpected error: " + e.getLocalizedMessage(); - status = HttpStatus.INTERNAL_SERVER_ERROR; - } - - } catch (Exception rollbackException) { - msg = "Failed to rollback the configuration: " + rollbackException.getLocalizedMessage(); - status = HttpStatus.INTERNAL_SERVER_ERROR; - } - - return new ErrorResponse(msg, status); - } - - private ResponseEntity logAndResponse(ErrorResponse error) { - log.error(error.getMessage()); - applicationEventService.createEvent(error.getMessage(), ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(error.getStatus(), error.getMessage()); - } - } From e2a0b53c850d39ec952bcfa91b4fb231c993812c Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Fri, 13 Feb 2026 23:57:40 -0500 Subject: [PATCH 075/240] feat(as400): add IBM AS/400 log collector Implement standalone collector service for IBM AS/400 systems integration with UTMStack . --- as400/README.md | 50 ++ as400/agent/collector.pb.go | 858 +++++++++++++++++++++++++++++++ as400/agent/collector_grpc.pb.go | 370 +++++++++++++ as400/agent/common.pb.go | 413 +++++++++++++++ as400/agent/delete.go | 43 ++ as400/agent/ping.pb.go | 218 ++++++++ as400/agent/ping_grpc.pb.go | 114 ++++ as400/agent/ping_imp.go | 90 ++++ as400/agent/register.go | 63 +++ as400/agent/uninstall.go | 17 + as400/collector/as400.go | 235 +++++++++ as400/collector/config.go | 214 ++++++++ as400/config/config.go | 168 ++++++ as400/config/const.go | 28 + as400/conn/conn.go | 106 ++++ as400/database/db.go | 129 +++++ as400/go.mod | 71 +++ as400/go.sum | 194 +++++++ as400/logservice/processor.go | 268 ++++++++++ as400/main.go | 205 ++++++++ as400/models/data.go | 5 + as400/models/schema.go | 14 + as400/models/version.go | 6 + as400/serv/config.go | 17 + as400/serv/install.go | 29 ++ as400/serv/run.go | 21 + as400/serv/service.go | 72 +++ as400/serv/uninstall.go | 16 + as400/updater/config/config.go | 54 ++ as400/updater/config/const.go | 23 + as400/updater/go.mod | 49 ++ as400/updater/go.sum | 121 +++++ as400/updater/main.go | 44 ++ as400/updater/models/version.go | 6 + as400/updater/service/config.go | 15 + as400/updater/service/install.go | 52 ++ as400/updater/service/service.go | 43 ++ as400/updater/updates/update.go | 243 +++++++++ as400/updater/utils/cmd.go | 39 ++ as400/updater/utils/download.go | 48 ++ as400/updater/utils/files.go | 92 ++++ as400/updater/utils/logger.go | 20 + as400/updater/utils/services.go | 55 ++ as400/updater/utils/zip.go | 52 ++ as400/updates/dependencies.go | 34 ++ as400/utils/address.go | 24 + as400/utils/banner.go | 17 + as400/utils/cmd.go | 12 + as400/utils/crypt.go | 22 + as400/utils/delay.go | 11 + as400/utils/download.go | 73 +++ as400/utils/files.go | 110 ++++ as400/utils/host.go | 21 + as400/utils/logger.go | 44 ++ as400/utils/os.go | 62 +++ as400/utils/port.go | 29 ++ as400/utils/services.go | 71 +++ as400/utils/updater.go | 31 ++ as400/version.json | 4 + 59 files changed, 5555 insertions(+) create mode 100644 as400/README.md create mode 100644 as400/agent/collector.pb.go create mode 100644 as400/agent/collector_grpc.pb.go create mode 100644 as400/agent/common.pb.go create mode 100644 as400/agent/delete.go create mode 100644 as400/agent/ping.pb.go create mode 100644 as400/agent/ping_grpc.pb.go create mode 100644 as400/agent/ping_imp.go create mode 100644 as400/agent/register.go create mode 100644 as400/agent/uninstall.go create mode 100644 as400/collector/as400.go create mode 100644 as400/collector/config.go create mode 100644 as400/config/config.go create mode 100644 as400/config/const.go create mode 100644 as400/conn/conn.go create mode 100644 as400/database/db.go create mode 100644 as400/go.mod create mode 100644 as400/go.sum create mode 100644 as400/logservice/processor.go create mode 100644 as400/main.go create mode 100644 as400/models/data.go create mode 100644 as400/models/schema.go create mode 100644 as400/models/version.go create mode 100644 as400/serv/config.go create mode 100644 as400/serv/install.go create mode 100644 as400/serv/run.go create mode 100644 as400/serv/service.go create mode 100644 as400/serv/uninstall.go create mode 100644 as400/updater/config/config.go create mode 100644 as400/updater/config/const.go create mode 100644 as400/updater/go.mod create mode 100644 as400/updater/go.sum create mode 100644 as400/updater/main.go create mode 100644 as400/updater/models/version.go create mode 100644 as400/updater/service/config.go create mode 100644 as400/updater/service/install.go create mode 100644 as400/updater/service/service.go create mode 100644 as400/updater/updates/update.go create mode 100644 as400/updater/utils/cmd.go create mode 100644 as400/updater/utils/download.go create mode 100644 as400/updater/utils/files.go create mode 100644 as400/updater/utils/logger.go create mode 100644 as400/updater/utils/services.go create mode 100644 as400/updater/utils/zip.go create mode 100644 as400/updates/dependencies.go create mode 100644 as400/utils/address.go create mode 100644 as400/utils/banner.go create mode 100644 as400/utils/cmd.go create mode 100644 as400/utils/crypt.go create mode 100644 as400/utils/delay.go create mode 100644 as400/utils/download.go create mode 100644 as400/utils/files.go create mode 100644 as400/utils/host.go create mode 100644 as400/utils/logger.go create mode 100644 as400/utils/os.go create mode 100644 as400/utils/port.go create mode 100644 as400/utils/services.go create mode 100644 as400/utils/updater.go create mode 100644 as400/version.json diff --git a/as400/README.md b/as400/README.md new file mode 100644 index 000000000..ad53f9f1f --- /dev/null +++ b/as400/README.md @@ -0,0 +1,50 @@ +# UTMStack AS400 Collector + +Log collection service for IBM AS/400 (iSeries) systems that integrates with the UTMStack platform for security analysis and event correlation. + +## General Description + +UTMStack AS400 Collector is a service written in Go that acts as a bridge between IBM AS/400 systems and the UTMStack platform. The service is installed on an intermediate server, connects to multiple remotely configured AS/400 systems, collects security logs, and transmits them in real-time to the UTMStack server for analysis. + +### Key Features + +- **Multi-Server Collection**: Support for multiple AS/400 systems simultaneously +- **Remote Configuration**: Management of AS/400 servers from the UTMStack panel via gRPC streaming +- **Local Persistence**: Temporary log storage in SQLite to ensure delivery in case of network failures +- **Auto-Updates**: Automatic update service included +- **Automatic Reconnection**: Robust handling of disconnections with automatic retries +- **Configurable Retention**: Control of local database size by retention in megabytes +- **Security**: AES encryption for credentials and TLS communication with the server + +## Requirements + +- **Operating System**: Linux (recommended) +- **Connectivity**: Network access to: + - UTMStack server (ports 9000, 9001, 50051) + - AS/400 systems to monitor +- **Java**: Installed automatically during installation +- **Privileges**: Administrator/root permissions to install the service + +### Installation Process + +1. Verify connectivity with the UTMStack server +2. Download dependencies (collector Java JAR, updater) +3. Install Java Runtime if necessary +4. Register the collector with UTMStack's Agent Manager +5. Create and enable the system service +6. Install the auto-update service + +## Configuration of AS/400 Servers + +Configuration of AS/400 servers to monitor is performed **from the UTMStack panel**, not locally. The collector automatically receives configuration. + +### Parameters per Server + +- **Tenant**: Identifier name of the group/server +- **Hostname**: IP address or hostname of the AS/400 +- **User ID**: Connection user to the AS/400 +- **Password**: Password (automatically encrypted) + +## License + +This project is part of UTMStack. Consult the main project license for more information. \ No newline at end of file diff --git a/as400/agent/collector.pb.go b/as400/agent/collector.pb.go new file mode 100644 index 000000000..bcf8d63b7 --- /dev/null +++ b/as400/agent/collector.pb.go @@ -0,0 +1,858 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.9 +// protoc v3.21.12 +// source: collector.proto + +package agent + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + 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) +) + +type CollectorModule int32 + +const ( + CollectorModule_AS_400 CollectorModule = 0 + CollectorModule_UTMSTACK CollectorModule = 1 +) + +// Enum value maps for CollectorModule. +var ( + CollectorModule_name = map[int32]string{ + 0: "AS_400", + 1: "UTMSTACK", + } + CollectorModule_value = map[string]int32{ + "AS_400": 0, + "UTMSTACK": 1, + } +) + +func (x CollectorModule) Enum() *CollectorModule { + p := new(CollectorModule) + *p = x + return p +} + +func (x CollectorModule) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CollectorModule) Descriptor() protoreflect.EnumDescriptor { + return file_collector_proto_enumTypes[0].Descriptor() +} + +func (CollectorModule) Type() protoreflect.EnumType { + return &file_collector_proto_enumTypes[0] +} + +func (x CollectorModule) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CollectorModule.Descriptor instead. +func (CollectorModule) EnumDescriptor() ([]byte, []int) { + return file_collector_proto_rawDescGZIP(), []int{0} +} + +type RegisterRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + Collector CollectorModule `protobuf:"varint,4,opt,name=collector,proto3,enum=agent.CollectorModule" json:"collector,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterRequest) Reset() { + *x = RegisterRequest{} + mi := &file_collector_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterRequest) ProtoMessage() {} + +func (x *RegisterRequest) ProtoReflect() protoreflect.Message { + mi := &file_collector_proto_msgTypes[0] + 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 RegisterRequest.ProtoReflect.Descriptor instead. +func (*RegisterRequest) Descriptor() ([]byte, []int) { + return file_collector_proto_rawDescGZIP(), []int{0} +} + +func (x *RegisterRequest) GetIp() string { + if x != nil { + return x.Ip + } + return "" +} + +func (x *RegisterRequest) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *RegisterRequest) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *RegisterRequest) GetCollector() CollectorModule { + if x != nil { + return x.Collector + } + return CollectorModule_AS_400 +} + +type ListCollectorResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Rows []*Collector `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListCollectorResponse) Reset() { + *x = ListCollectorResponse{} + mi := &file_collector_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListCollectorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCollectorResponse) ProtoMessage() {} + +func (x *ListCollectorResponse) ProtoReflect() protoreflect.Message { + mi := &file_collector_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 ListCollectorResponse.ProtoReflect.Descriptor instead. +func (*ListCollectorResponse) Descriptor() ([]byte, []int) { + return file_collector_proto_rawDescGZIP(), []int{1} +} + +func (x *ListCollectorResponse) GetRows() []*Collector { + if x != nil { + return x.Rows + } + return nil +} + +func (x *ListCollectorResponse) GetTotal() int32 { + if x != nil { + return x.Total + } + return 0 +} + +type Collector struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Status Status `protobuf:"varint,2,opt,name=status,proto3,enum=agent.Status" json:"status,omitempty"` + CollectorKey string `protobuf:"bytes,3,opt,name=collector_key,json=collectorKey,proto3" json:"collector_key,omitempty"` + Ip string `protobuf:"bytes,4,opt,name=ip,proto3" json:"ip,omitempty"` + Hostname string `protobuf:"bytes,5,opt,name=hostname,proto3" json:"hostname,omitempty"` + Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` + Module CollectorModule `protobuf:"varint,7,opt,name=module,proto3,enum=agent.CollectorModule" json:"module,omitempty"` + LastSeen string `protobuf:"bytes,8,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Collector) Reset() { + *x = Collector{} + mi := &file_collector_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Collector) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Collector) ProtoMessage() {} + +func (x *Collector) ProtoReflect() protoreflect.Message { + mi := &file_collector_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 Collector.ProtoReflect.Descriptor instead. +func (*Collector) Descriptor() ([]byte, []int) { + return file_collector_proto_rawDescGZIP(), []int{2} +} + +func (x *Collector) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Collector) GetStatus() Status { + if x != nil { + return x.Status + } + return Status_ONLINE +} + +func (x *Collector) GetCollectorKey() string { + if x != nil { + return x.CollectorKey + } + return "" +} + +func (x *Collector) GetIp() string { + if x != nil { + return x.Ip + } + return "" +} + +func (x *Collector) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *Collector) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *Collector) GetModule() CollectorModule { + if x != nil { + return x.Module + } + return CollectorModule_AS_400 +} + +func (x *Collector) GetLastSeen() string { + if x != nil { + return x.LastSeen + } + return "" +} + +type CollectorMessages struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to StreamMessage: + // + // *CollectorMessages_Config + // *CollectorMessages_Result + StreamMessage isCollectorMessages_StreamMessage `protobuf_oneof:"stream_message"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CollectorMessages) Reset() { + *x = CollectorMessages{} + mi := &file_collector_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CollectorMessages) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CollectorMessages) ProtoMessage() {} + +func (x *CollectorMessages) ProtoReflect() protoreflect.Message { + mi := &file_collector_proto_msgTypes[3] + 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 CollectorMessages.ProtoReflect.Descriptor instead. +func (*CollectorMessages) Descriptor() ([]byte, []int) { + return file_collector_proto_rawDescGZIP(), []int{3} +} + +func (x *CollectorMessages) GetStreamMessage() isCollectorMessages_StreamMessage { + if x != nil { + return x.StreamMessage + } + return nil +} + +func (x *CollectorMessages) GetConfig() *CollectorConfig { + if x != nil { + if x, ok := x.StreamMessage.(*CollectorMessages_Config); ok { + return x.Config + } + } + return nil +} + +func (x *CollectorMessages) GetResult() *ConfigKnowledge { + if x != nil { + if x, ok := x.StreamMessage.(*CollectorMessages_Result); ok { + return x.Result + } + } + return nil +} + +type isCollectorMessages_StreamMessage interface { + isCollectorMessages_StreamMessage() +} + +type CollectorMessages_Config struct { + Config *CollectorConfig `protobuf:"bytes,1,opt,name=config,proto3,oneof"` +} + +type CollectorMessages_Result struct { + Result *ConfigKnowledge `protobuf:"bytes,2,opt,name=result,proto3,oneof"` +} + +func (*CollectorMessages_Config) isCollectorMessages_StreamMessage() {} + +func (*CollectorMessages_Result) isCollectorMessages_StreamMessage() {} + +type CollectorConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + CollectorId string `protobuf:"bytes,1,opt,name=collector_id,json=collectorId,proto3" json:"collector_id,omitempty"` + Groups []*CollectorConfigGroup `protobuf:"bytes,2,rep,name=groups,proto3" json:"groups,omitempty"` + RequestId string `protobuf:"bytes,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CollectorConfig) Reset() { + *x = CollectorConfig{} + mi := &file_collector_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CollectorConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CollectorConfig) ProtoMessage() {} + +func (x *CollectorConfig) ProtoReflect() protoreflect.Message { + mi := &file_collector_proto_msgTypes[4] + 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 CollectorConfig.ProtoReflect.Descriptor instead. +func (*CollectorConfig) Descriptor() ([]byte, []int) { + return file_collector_proto_rawDescGZIP(), []int{4} +} + +func (x *CollectorConfig) GetCollectorId() string { + if x != nil { + return x.CollectorId + } + return "" +} + +func (x *CollectorConfig) GetGroups() []*CollectorConfigGroup { + if x != nil { + return x.Groups + } + return nil +} + +func (x *CollectorConfig) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +type CollectorConfigGroup struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + GroupName string `protobuf:"bytes,2,opt,name=group_name,json=groupName,proto3" json:"group_name,omitempty"` + GroupDescription string `protobuf:"bytes,3,opt,name=group_description,json=groupDescription,proto3" json:"group_description,omitempty"` + Configurations []*CollectorGroupConfigurations `protobuf:"bytes,4,rep,name=configurations,proto3" json:"configurations,omitempty"` + CollectorId int32 `protobuf:"varint,5,opt,name=collector_id,json=collectorId,proto3" json:"collector_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CollectorConfigGroup) Reset() { + *x = CollectorConfigGroup{} + mi := &file_collector_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CollectorConfigGroup) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CollectorConfigGroup) ProtoMessage() {} + +func (x *CollectorConfigGroup) ProtoReflect() protoreflect.Message { + mi := &file_collector_proto_msgTypes[5] + 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 CollectorConfigGroup.ProtoReflect.Descriptor instead. +func (*CollectorConfigGroup) Descriptor() ([]byte, []int) { + return file_collector_proto_rawDescGZIP(), []int{5} +} + +func (x *CollectorConfigGroup) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *CollectorConfigGroup) GetGroupName() string { + if x != nil { + return x.GroupName + } + return "" +} + +func (x *CollectorConfigGroup) GetGroupDescription() string { + if x != nil { + return x.GroupDescription + } + return "" +} + +func (x *CollectorConfigGroup) GetConfigurations() []*CollectorGroupConfigurations { + if x != nil { + return x.Configurations + } + return nil +} + +func (x *CollectorConfigGroup) GetCollectorId() int32 { + if x != nil { + return x.CollectorId + } + return 0 +} + +type CollectorGroupConfigurations struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + GroupId int32 `protobuf:"varint,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + ConfKey string `protobuf:"bytes,3,opt,name=conf_key,json=confKey,proto3" json:"conf_key,omitempty"` + ConfValue string `protobuf:"bytes,4,opt,name=conf_value,json=confValue,proto3" json:"conf_value,omitempty"` + ConfName string `protobuf:"bytes,5,opt,name=conf_name,json=confName,proto3" json:"conf_name,omitempty"` + ConfDescription string `protobuf:"bytes,6,opt,name=conf_description,json=confDescription,proto3" json:"conf_description,omitempty"` + ConfDataType string `protobuf:"bytes,7,opt,name=conf_data_type,json=confDataType,proto3" json:"conf_data_type,omitempty"` + ConfRequired bool `protobuf:"varint,8,opt,name=conf_required,json=confRequired,proto3" json:"conf_required,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CollectorGroupConfigurations) Reset() { + *x = CollectorGroupConfigurations{} + mi := &file_collector_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CollectorGroupConfigurations) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CollectorGroupConfigurations) ProtoMessage() {} + +func (x *CollectorGroupConfigurations) ProtoReflect() protoreflect.Message { + mi := &file_collector_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 CollectorGroupConfigurations.ProtoReflect.Descriptor instead. +func (*CollectorGroupConfigurations) Descriptor() ([]byte, []int) { + return file_collector_proto_rawDescGZIP(), []int{6} +} + +func (x *CollectorGroupConfigurations) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *CollectorGroupConfigurations) GetGroupId() int32 { + if x != nil { + return x.GroupId + } + return 0 +} + +func (x *CollectorGroupConfigurations) GetConfKey() string { + if x != nil { + return x.ConfKey + } + return "" +} + +func (x *CollectorGroupConfigurations) GetConfValue() string { + if x != nil { + return x.ConfValue + } + return "" +} + +func (x *CollectorGroupConfigurations) GetConfName() string { + if x != nil { + return x.ConfName + } + return "" +} + +func (x *CollectorGroupConfigurations) GetConfDescription() string { + if x != nil { + return x.ConfDescription + } + return "" +} + +func (x *CollectorGroupConfigurations) GetConfDataType() string { + if x != nil { + return x.ConfDataType + } + return "" +} + +func (x *CollectorGroupConfigurations) GetConfRequired() bool { + if x != nil { + return x.ConfRequired + } + return false +} + +type ConfigKnowledge struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accepted string `protobuf:"bytes,1,opt,name=accepted,proto3" json:"accepted,omitempty"` + RequestId string `protobuf:"bytes,2,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfigKnowledge) Reset() { + *x = ConfigKnowledge{} + mi := &file_collector_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfigKnowledge) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigKnowledge) ProtoMessage() {} + +func (x *ConfigKnowledge) ProtoReflect() protoreflect.Message { + mi := &file_collector_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 ConfigKnowledge.ProtoReflect.Descriptor instead. +func (*ConfigKnowledge) Descriptor() ([]byte, []int) { + return file_collector_proto_rawDescGZIP(), []int{7} +} + +func (x *ConfigKnowledge) GetAccepted() string { + if x != nil { + return x.Accepted + } + return "" +} + +func (x *ConfigKnowledge) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +type ConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Module CollectorModule `protobuf:"varint,1,opt,name=module,proto3,enum=agent.CollectorModule" json:"module,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfigRequest) Reset() { + *x = ConfigRequest{} + mi := &file_collector_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigRequest) ProtoMessage() {} + +func (x *ConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_collector_proto_msgTypes[8] + 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 ConfigRequest.ProtoReflect.Descriptor instead. +func (*ConfigRequest) Descriptor() ([]byte, []int) { + return file_collector_proto_rawDescGZIP(), []int{8} +} + +func (x *ConfigRequest) GetModule() CollectorModule { + if x != nil { + return x.Module + } + return CollectorModule_AS_400 +} + +var File_collector_proto protoreflect.FileDescriptor + +const file_collector_proto_rawDesc = "" + + "\n" + + "\x0fcollector.proto\x12\x05agent\x1a\fcommon.proto\"\x8d\x01\n" + + "\x0fRegisterRequest\x12\x0e\n" + + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12\x18\n" + + "\aversion\x18\x03 \x01(\tR\aversion\x124\n" + + "\tcollector\x18\x04 \x01(\x0e2\x16.agent.CollectorModuleR\tcollector\"S\n" + + "\x15ListCollectorResponse\x12$\n" + + "\x04rows\x18\x01 \x03(\v2\x10.agent.CollectorR\x04rows\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"\xfa\x01\n" + + "\tCollector\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x05R\x02id\x12%\n" + + "\x06status\x18\x02 \x01(\x0e2\r.agent.StatusR\x06status\x12#\n" + + "\rcollector_key\x18\x03 \x01(\tR\fcollectorKey\x12\x0e\n" + + "\x02ip\x18\x04 \x01(\tR\x02ip\x12\x1a\n" + + "\bhostname\x18\x05 \x01(\tR\bhostname\x12\x18\n" + + "\aversion\x18\x06 \x01(\tR\aversion\x12.\n" + + "\x06module\x18\a \x01(\x0e2\x16.agent.CollectorModuleR\x06module\x12\x1b\n" + + "\tlast_seen\x18\b \x01(\tR\blastSeen\"\x89\x01\n" + + "\x11CollectorMessages\x120\n" + + "\x06config\x18\x01 \x01(\v2\x16.agent.CollectorConfigH\x00R\x06config\x120\n" + + "\x06result\x18\x02 \x01(\v2\x16.agent.ConfigKnowledgeH\x00R\x06resultB\x10\n" + + "\x0estream_message\"\x88\x01\n" + + "\x0fCollectorConfig\x12!\n" + + "\fcollector_id\x18\x01 \x01(\tR\vcollectorId\x123\n" + + "\x06groups\x18\x02 \x03(\v2\x1b.agent.CollectorConfigGroupR\x06groups\x12\x1d\n" + + "\n" + + "request_id\x18\x03 \x01(\tR\trequestId\"\xe2\x01\n" + + "\x14CollectorConfigGroup\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x05R\x02id\x12\x1d\n" + + "\n" + + "group_name\x18\x02 \x01(\tR\tgroupName\x12+\n" + + "\x11group_description\x18\x03 \x01(\tR\x10groupDescription\x12K\n" + + "\x0econfigurations\x18\x04 \x03(\v2#.agent.CollectorGroupConfigurationsR\x0econfigurations\x12!\n" + + "\fcollector_id\x18\x05 \x01(\x05R\vcollectorId\"\x96\x02\n" + + "\x1cCollectorGroupConfigurations\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x05R\x02id\x12\x19\n" + + "\bgroup_id\x18\x02 \x01(\x05R\agroupId\x12\x19\n" + + "\bconf_key\x18\x03 \x01(\tR\aconfKey\x12\x1d\n" + + "\n" + + "conf_value\x18\x04 \x01(\tR\tconfValue\x12\x1b\n" + + "\tconf_name\x18\x05 \x01(\tR\bconfName\x12)\n" + + "\x10conf_description\x18\x06 \x01(\tR\x0fconfDescription\x12$\n" + + "\x0econf_data_type\x18\a \x01(\tR\fconfDataType\x12#\n" + + "\rconf_required\x18\b \x01(\bR\fconfRequired\"L\n" + + "\x0fConfigKnowledge\x12\x1a\n" + + "\baccepted\x18\x01 \x01(\tR\baccepted\x12\x1d\n" + + "\n" + + "request_id\x18\x02 \x01(\tR\trequestId\"?\n" + + "\rConfigRequest\x12.\n" + + "\x06module\x18\x01 \x01(\x0e2\x16.agent.CollectorModuleR\x06module*+\n" + + "\x0fCollectorModule\x12\n" + + "\n" + + "\x06AS_400\x10\x00\x12\f\n" + + "\bUTMSTACK\x10\x012\xee\x02\n" + + "\x10CollectorService\x12B\n" + + "\x11RegisterCollector\x12\x16.agent.RegisterRequest\x1a\x13.agent.AuthResponse\"\x00\x12>\n" + + "\x0fDeleteCollector\x12\x14.agent.DeleteRequest\x1a\x13.agent.AuthResponse\"\x00\x12C\n" + + "\rListCollector\x12\x12.agent.ListRequest\x1a\x1c.agent.ListCollectorResponse\"\x00\x12K\n" + + "\x0fCollectorStream\x12\x18.agent.CollectorMessages\x1a\x18.agent.CollectorMessages\"\x00(\x010\x01\x12D\n" + + "\x12GetCollectorConfig\x12\x14.agent.ConfigRequest\x1a\x16.agent.CollectorConfig\"\x002d\n" + + "\x15PanelCollectorService\x12K\n" + + "\x17RegisterCollectorConfig\x12\x16.agent.CollectorConfig\x1a\x16.agent.ConfigKnowledge\"\x00B5Z3github.com/utmstack/UTMStack/docker-collector/agentb\x06proto3" + +var ( + file_collector_proto_rawDescOnce sync.Once + file_collector_proto_rawDescData []byte +) + +func file_collector_proto_rawDescGZIP() []byte { + file_collector_proto_rawDescOnce.Do(func() { + file_collector_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_collector_proto_rawDesc), len(file_collector_proto_rawDesc))) + }) + return file_collector_proto_rawDescData +} + +var file_collector_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_collector_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_collector_proto_goTypes = []any{ + (CollectorModule)(0), // 0: agent.CollectorModule + (*RegisterRequest)(nil), // 1: agent.RegisterRequest + (*ListCollectorResponse)(nil), // 2: agent.ListCollectorResponse + (*Collector)(nil), // 3: agent.Collector + (*CollectorMessages)(nil), // 4: agent.CollectorMessages + (*CollectorConfig)(nil), // 5: agent.CollectorConfig + (*CollectorConfigGroup)(nil), // 6: agent.CollectorConfigGroup + (*CollectorGroupConfigurations)(nil), // 7: agent.CollectorGroupConfigurations + (*ConfigKnowledge)(nil), // 8: agent.ConfigKnowledge + (*ConfigRequest)(nil), // 9: agent.ConfigRequest + (Status)(0), // 10: agent.Status + (*DeleteRequest)(nil), // 11: agent.DeleteRequest + (*ListRequest)(nil), // 12: agent.ListRequest + (*AuthResponse)(nil), // 13: agent.AuthResponse +} +var file_collector_proto_depIdxs = []int32{ + 0, // 0: agent.RegisterRequest.collector:type_name -> agent.CollectorModule + 3, // 1: agent.ListCollectorResponse.rows:type_name -> agent.Collector + 10, // 2: agent.Collector.status:type_name -> agent.Status + 0, // 3: agent.Collector.module:type_name -> agent.CollectorModule + 5, // 4: agent.CollectorMessages.config:type_name -> agent.CollectorConfig + 8, // 5: agent.CollectorMessages.result:type_name -> agent.ConfigKnowledge + 6, // 6: agent.CollectorConfig.groups:type_name -> agent.CollectorConfigGroup + 7, // 7: agent.CollectorConfigGroup.configurations:type_name -> agent.CollectorGroupConfigurations + 0, // 8: agent.ConfigRequest.module:type_name -> agent.CollectorModule + 1, // 9: agent.CollectorService.RegisterCollector:input_type -> agent.RegisterRequest + 11, // 10: agent.CollectorService.DeleteCollector:input_type -> agent.DeleteRequest + 12, // 11: agent.CollectorService.ListCollector:input_type -> agent.ListRequest + 4, // 12: agent.CollectorService.CollectorStream:input_type -> agent.CollectorMessages + 9, // 13: agent.CollectorService.GetCollectorConfig:input_type -> agent.ConfigRequest + 5, // 14: agent.PanelCollectorService.RegisterCollectorConfig:input_type -> agent.CollectorConfig + 13, // 15: agent.CollectorService.RegisterCollector:output_type -> agent.AuthResponse + 13, // 16: agent.CollectorService.DeleteCollector:output_type -> agent.AuthResponse + 2, // 17: agent.CollectorService.ListCollector:output_type -> agent.ListCollectorResponse + 4, // 18: agent.CollectorService.CollectorStream:output_type -> agent.CollectorMessages + 5, // 19: agent.CollectorService.GetCollectorConfig:output_type -> agent.CollectorConfig + 8, // 20: agent.PanelCollectorService.RegisterCollectorConfig:output_type -> agent.ConfigKnowledge + 15, // [15:21] is the sub-list for method output_type + 9, // [9:15] 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 +} + +func init() { file_collector_proto_init() } +func file_collector_proto_init() { + if File_collector_proto != nil { + return + } + file_common_proto_init() + file_collector_proto_msgTypes[3].OneofWrappers = []any{ + (*CollectorMessages_Config)(nil), + (*CollectorMessages_Result)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_collector_proto_rawDesc), len(file_collector_proto_rawDesc)), + NumEnums: 1, + NumMessages: 9, + NumExtensions: 0, + NumServices: 2, + }, + GoTypes: file_collector_proto_goTypes, + DependencyIndexes: file_collector_proto_depIdxs, + EnumInfos: file_collector_proto_enumTypes, + MessageInfos: file_collector_proto_msgTypes, + }.Build() + File_collector_proto = out.File + file_collector_proto_goTypes = nil + file_collector_proto_depIdxs = nil +} diff --git a/as400/agent/collector_grpc.pb.go b/as400/agent/collector_grpc.pb.go new file mode 100644 index 000000000..a924c7361 --- /dev/null +++ b/as400/agent/collector_grpc.pb.go @@ -0,0 +1,370 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.21.12 +// source: collector.proto + +package agent + +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 ( + CollectorService_RegisterCollector_FullMethodName = "/agent.CollectorService/RegisterCollector" + CollectorService_DeleteCollector_FullMethodName = "/agent.CollectorService/DeleteCollector" + CollectorService_ListCollector_FullMethodName = "/agent.CollectorService/ListCollector" + CollectorService_CollectorStream_FullMethodName = "/agent.CollectorService/CollectorStream" + CollectorService_GetCollectorConfig_FullMethodName = "/agent.CollectorService/GetCollectorConfig" +) + +// CollectorServiceClient is the client API for CollectorService 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 CollectorServiceClient interface { + RegisterCollector(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*AuthResponse, error) + DeleteCollector(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*AuthResponse, error) + ListCollector(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListCollectorResponse, error) + CollectorStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[CollectorMessages, CollectorMessages], error) + GetCollectorConfig(ctx context.Context, in *ConfigRequest, opts ...grpc.CallOption) (*CollectorConfig, error) +} + +type collectorServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCollectorServiceClient(cc grpc.ClientConnInterface) CollectorServiceClient { + return &collectorServiceClient{cc} +} + +func (c *collectorServiceClient) RegisterCollector(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*AuthResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthResponse) + err := c.cc.Invoke(ctx, CollectorService_RegisterCollector_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *collectorServiceClient) DeleteCollector(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*AuthResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AuthResponse) + err := c.cc.Invoke(ctx, CollectorService_DeleteCollector_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *collectorServiceClient) ListCollector(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListCollectorResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListCollectorResponse) + err := c.cc.Invoke(ctx, CollectorService_ListCollector_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *collectorServiceClient) CollectorStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[CollectorMessages, CollectorMessages], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CollectorService_ServiceDesc.Streams[0], CollectorService_CollectorStream_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[CollectorMessages, CollectorMessages]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CollectorService_CollectorStreamClient = grpc.BidiStreamingClient[CollectorMessages, CollectorMessages] + +func (c *collectorServiceClient) GetCollectorConfig(ctx context.Context, in *ConfigRequest, opts ...grpc.CallOption) (*CollectorConfig, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CollectorConfig) + err := c.cc.Invoke(ctx, CollectorService_GetCollectorConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CollectorServiceServer is the server API for CollectorService service. +// All implementations must embed UnimplementedCollectorServiceServer +// for forward compatibility. +type CollectorServiceServer interface { + RegisterCollector(context.Context, *RegisterRequest) (*AuthResponse, error) + DeleteCollector(context.Context, *DeleteRequest) (*AuthResponse, error) + ListCollector(context.Context, *ListRequest) (*ListCollectorResponse, error) + CollectorStream(grpc.BidiStreamingServer[CollectorMessages, CollectorMessages]) error + GetCollectorConfig(context.Context, *ConfigRequest) (*CollectorConfig, error) + mustEmbedUnimplementedCollectorServiceServer() +} + +// UnimplementedCollectorServiceServer must 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 UnimplementedCollectorServiceServer struct{} + +func (UnimplementedCollectorServiceServer) RegisterCollector(context.Context, *RegisterRequest) (*AuthResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterCollector not implemented") +} +func (UnimplementedCollectorServiceServer) DeleteCollector(context.Context, *DeleteRequest) (*AuthResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteCollector not implemented") +} +func (UnimplementedCollectorServiceServer) ListCollector(context.Context, *ListRequest) (*ListCollectorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListCollector not implemented") +} +func (UnimplementedCollectorServiceServer) CollectorStream(grpc.BidiStreamingServer[CollectorMessages, CollectorMessages]) error { + return status.Errorf(codes.Unimplemented, "method CollectorStream not implemented") +} +func (UnimplementedCollectorServiceServer) GetCollectorConfig(context.Context, *ConfigRequest) (*CollectorConfig, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCollectorConfig not implemented") +} +func (UnimplementedCollectorServiceServer) mustEmbedUnimplementedCollectorServiceServer() {} +func (UnimplementedCollectorServiceServer) testEmbeddedByValue() {} + +// UnsafeCollectorServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CollectorServiceServer will +// result in compilation errors. +type UnsafeCollectorServiceServer interface { + mustEmbedUnimplementedCollectorServiceServer() +} + +func RegisterCollectorServiceServer(s grpc.ServiceRegistrar, srv CollectorServiceServer) { + // If the following call pancis, it indicates UnimplementedCollectorServiceServer 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(&CollectorService_ServiceDesc, srv) +} + +func _CollectorService_RegisterCollector_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CollectorServiceServer).RegisterCollector(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CollectorService_RegisterCollector_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CollectorServiceServer).RegisterCollector(ctx, req.(*RegisterRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CollectorService_DeleteCollector_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CollectorServiceServer).DeleteCollector(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CollectorService_DeleteCollector_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CollectorServiceServer).DeleteCollector(ctx, req.(*DeleteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CollectorService_ListCollector_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CollectorServiceServer).ListCollector(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CollectorService_ListCollector_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CollectorServiceServer).ListCollector(ctx, req.(*ListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CollectorService_CollectorStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(CollectorServiceServer).CollectorStream(&grpc.GenericServerStream[CollectorMessages, CollectorMessages]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CollectorService_CollectorStreamServer = grpc.BidiStreamingServer[CollectorMessages, CollectorMessages] + +func _CollectorService_GetCollectorConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CollectorServiceServer).GetCollectorConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CollectorService_GetCollectorConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CollectorServiceServer).GetCollectorConfig(ctx, req.(*ConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CollectorService_ServiceDesc is the grpc.ServiceDesc for CollectorService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CollectorService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "agent.CollectorService", + HandlerType: (*CollectorServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterCollector", + Handler: _CollectorService_RegisterCollector_Handler, + }, + { + MethodName: "DeleteCollector", + Handler: _CollectorService_DeleteCollector_Handler, + }, + { + MethodName: "ListCollector", + Handler: _CollectorService_ListCollector_Handler, + }, + { + MethodName: "GetCollectorConfig", + Handler: _CollectorService_GetCollectorConfig_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "CollectorStream", + Handler: _CollectorService_CollectorStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "collector.proto", +} + +const ( + PanelCollectorService_RegisterCollectorConfig_FullMethodName = "/agent.PanelCollectorService/RegisterCollectorConfig" +) + +// PanelCollectorServiceClient is the client API for PanelCollectorService 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 PanelCollectorServiceClient interface { + RegisterCollectorConfig(ctx context.Context, in *CollectorConfig, opts ...grpc.CallOption) (*ConfigKnowledge, error) +} + +type panelCollectorServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPanelCollectorServiceClient(cc grpc.ClientConnInterface) PanelCollectorServiceClient { + return &panelCollectorServiceClient{cc} +} + +func (c *panelCollectorServiceClient) RegisterCollectorConfig(ctx context.Context, in *CollectorConfig, opts ...grpc.CallOption) (*ConfigKnowledge, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ConfigKnowledge) + err := c.cc.Invoke(ctx, PanelCollectorService_RegisterCollectorConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PanelCollectorServiceServer is the server API for PanelCollectorService service. +// All implementations must embed UnimplementedPanelCollectorServiceServer +// for forward compatibility. +type PanelCollectorServiceServer interface { + RegisterCollectorConfig(context.Context, *CollectorConfig) (*ConfigKnowledge, error) + mustEmbedUnimplementedPanelCollectorServiceServer() +} + +// UnimplementedPanelCollectorServiceServer must 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 UnimplementedPanelCollectorServiceServer struct{} + +func (UnimplementedPanelCollectorServiceServer) RegisterCollectorConfig(context.Context, *CollectorConfig) (*ConfigKnowledge, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterCollectorConfig not implemented") +} +func (UnimplementedPanelCollectorServiceServer) mustEmbedUnimplementedPanelCollectorServiceServer() {} +func (UnimplementedPanelCollectorServiceServer) testEmbeddedByValue() {} + +// UnsafePanelCollectorServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PanelCollectorServiceServer will +// result in compilation errors. +type UnsafePanelCollectorServiceServer interface { + mustEmbedUnimplementedPanelCollectorServiceServer() +} + +func RegisterPanelCollectorServiceServer(s grpc.ServiceRegistrar, srv PanelCollectorServiceServer) { + // If the following call pancis, it indicates UnimplementedPanelCollectorServiceServer 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(&PanelCollectorService_ServiceDesc, srv) +} + +func _PanelCollectorService_RegisterCollectorConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CollectorConfig) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PanelCollectorServiceServer).RegisterCollectorConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PanelCollectorService_RegisterCollectorConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PanelCollectorServiceServer).RegisterCollectorConfig(ctx, req.(*CollectorConfig)) + } + return interceptor(ctx, in, info, handler) +} + +// PanelCollectorService_ServiceDesc is the grpc.ServiceDesc for PanelCollectorService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PanelCollectorService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "agent.PanelCollectorService", + HandlerType: (*PanelCollectorServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterCollectorConfig", + Handler: _PanelCollectorService_RegisterCollectorConfig_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "collector.proto", +} diff --git a/as400/agent/common.pb.go b/as400/agent/common.pb.go new file mode 100644 index 000000000..0d4ccf71d --- /dev/null +++ b/as400/agent/common.pb.go @@ -0,0 +1,413 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 +// source: common.proto + +package agent + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +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) +) + +type Status int32 + +const ( + Status_ONLINE Status = 0 + Status_OFFLINE Status = 1 + Status_UNKNOWN Status = 2 +) + +// Enum value maps for Status. +var ( + Status_name = map[int32]string{ + 0: "ONLINE", + 1: "OFFLINE", + 2: "UNKNOWN", + } + Status_value = map[string]int32{ + "ONLINE": 0, + "OFFLINE": 1, + "UNKNOWN": 2, + } +) + +func (x Status) Enum() *Status { + p := new(Status) + *p = x + return p +} + +func (x Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Status) Descriptor() protoreflect.EnumDescriptor { + return file_common_proto_enumTypes[0].Descriptor() +} + +func (Status) Type() protoreflect.EnumType { + return &file_common_proto_enumTypes[0] +} + +func (x Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Status.Descriptor instead. +func (Status) EnumDescriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{0} +} + +type ConnectorType int32 + +const ( + ConnectorType_AGENT ConnectorType = 0 + ConnectorType_COLLECTOR ConnectorType = 1 +) + +// Enum value maps for ConnectorType. +var ( + ConnectorType_name = map[int32]string{ + 0: "AGENT", + 1: "COLLECTOR", + } + ConnectorType_value = map[string]int32{ + "AGENT": 0, + "COLLECTOR": 1, + } +) + +func (x ConnectorType) Enum() *ConnectorType { + p := new(ConnectorType) + *p = x + return p +} + +func (x ConnectorType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ConnectorType) Descriptor() protoreflect.EnumDescriptor { + return file_common_proto_enumTypes[1].Descriptor() +} + +func (ConnectorType) Type() protoreflect.EnumType { + return &file_common_proto_enumTypes[1] +} + +func (x ConnectorType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ConnectorType.Descriptor instead. +func (ConnectorType) EnumDescriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{1} +} + +type ListRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PageNumber int32 `protobuf:"varint,1,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + SearchQuery string `protobuf:"bytes,3,opt,name=search_query,json=searchQuery,proto3" json:"search_query,omitempty"` + SortBy string `protobuf:"bytes,4,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` +} + +func (x *ListRequest) Reset() { + *x = ListRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRequest) ProtoMessage() {} + +func (x *ListRequest) ProtoReflect() protoreflect.Message { + mi := &file_common_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. +func (*ListRequest) Descriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{0} +} + +func (x *ListRequest) GetPageNumber() int32 { + if x != nil { + return x.PageNumber + } + return 0 +} + +func (x *ListRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListRequest) GetSearchQuery() string { + if x != nil { + return x.SearchQuery + } + return "" +} + +func (x *ListRequest) GetSortBy() string { + if x != nil { + return x.SortBy + } + return "" +} + +type AuthResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` +} + +func (x *AuthResponse) Reset() { + *x = AuthResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_common_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthResponse) ProtoMessage() {} + +func (x *AuthResponse) ProtoReflect() protoreflect.Message { + mi := &file_common_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthResponse.ProtoReflect.Descriptor instead. +func (*AuthResponse) Descriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{1} +} + +func (x *AuthResponse) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *AuthResponse) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +type DeleteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DeletedBy string `protobuf:"bytes,1,opt,name=deleted_by,json=deletedBy,proto3" json:"deleted_by,omitempty"` +} + +func (x *DeleteRequest) Reset() { + *x = DeleteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_common_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRequest) ProtoMessage() {} + +func (x *DeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_common_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. +func (*DeleteRequest) Descriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{2} +} + +func (x *DeleteRequest) GetDeletedBy() string { + if x != nil { + return x.DeletedBy + } + return "" +} + +var File_common_proto protoreflect.FileDescriptor + +var file_common_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x87, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x65, + 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x71, 0x75, + 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x22, + 0x30, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x22, 0x2e, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x42, + 0x79, 0x2a, 0x2e, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x4f, + 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x4f, 0x46, 0x46, 0x4c, 0x49, + 0x4e, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x02, 0x2a, 0x29, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, + 0x09, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x10, 0x01, 0x42, 0x32, 0x5a, 0x30, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_common_proto_rawDescOnce sync.Once + file_common_proto_rawDescData = file_common_proto_rawDesc +) + +func file_common_proto_rawDescGZIP() []byte { + file_common_proto_rawDescOnce.Do(func() { + file_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_proto_rawDescData) + }) + return file_common_proto_rawDescData +} + +var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_common_proto_goTypes = []interface{}{ + (Status)(0), // 0: agent.Status + (ConnectorType)(0), // 1: agent.ConnectorType + (*ListRequest)(nil), // 2: agent.ListRequest + (*AuthResponse)(nil), // 3: agent.AuthResponse + (*DeleteRequest)(nil), // 4: agent.DeleteRequest +} +var file_common_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 +} + +func init() { file_common_proto_init() } +func file_common_proto_init() { + if File_common_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AuthResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_common_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_common_proto_rawDesc, + NumEnums: 2, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_common_proto_goTypes, + DependencyIndexes: file_common_proto_depIdxs, + EnumInfos: file_common_proto_enumTypes, + MessageInfos: file_common_proto_msgTypes, + }.Build() + File_common_proto = out.File + file_common_proto_rawDesc = nil + file_common_proto_goTypes = nil + file_common_proto_depIdxs = nil +} diff --git a/as400/agent/delete.go b/as400/agent/delete.go new file mode 100644 index 000000000..2abf27b7e --- /dev/null +++ b/as400/agent/delete.go @@ -0,0 +1,43 @@ +package agent + +import ( + "context" + "os/user" + "strconv" + + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/conn" + "github.com/utmstack/UTMStack/as400/utils" + "google.golang.org/grpc/metadata" +) + +func DeleteAgent(cnf *config.Config) error { + connection, err := conn.GetAgentManagerConnection(cnf) + if err != nil { + return utils.Logger.ErrorF("error connecting to Agent Manager: %v", err) + } + + collectorClient := NewCollectorServiceClient(connection) + ctx, cancel := context.WithCancel(context.Background()) + ctx = metadata.AppendToOutgoingContext(ctx, "key", cnf.CollectorKey) + ctx = metadata.AppendToOutgoingContext(ctx, "id", strconv.Itoa(int(cnf.CollectorID))) + ctx = metadata.AppendToOutgoingContext(ctx, "type", "collector") + defer cancel() + + currentUser, err := user.Current() + if err != nil { + return utils.Logger.ErrorF("error getting user: %v", err) + } + + delReq := &DeleteRequest{ + DeletedBy: currentUser.Username, + } + + _, err = collectorClient.DeleteCollector(ctx, delReq) + if err != nil { + utils.Logger.ErrorF("error removing UTMStack AS400 Collector from Agent Manager %v", err) + } + + utils.Logger.LogF(100, "UTMStack AS400 Collector removed successfully from agent manager") + return nil +} diff --git a/as400/agent/ping.pb.go b/as400/agent/ping.pb.go new file mode 100644 index 000000000..21ddaf763 --- /dev/null +++ b/as400/agent/ping.pb.go @@ -0,0 +1,218 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 +// source: ping.proto + +package agent + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +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) +) + +type PingRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type ConnectorType `protobuf:"varint,1,opt,name=type,proto3,enum=agent.ConnectorType" json:"type,omitempty"` +} + +func (x *PingRequest) Reset() { + *x = PingRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ping_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingRequest) ProtoMessage() {} + +func (x *PingRequest) ProtoReflect() protoreflect.Message { + mi := &file_ping_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. +func (*PingRequest) Descriptor() ([]byte, []int) { + return file_ping_proto_rawDescGZIP(), []int{0} +} + +func (x *PingRequest) GetType() ConnectorType { + if x != nil { + return x.Type + } + return ConnectorType_AGENT +} + +type PingResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Received string `protobuf:"bytes,1,opt,name=received,proto3" json:"received,omitempty"` +} + +func (x *PingResponse) Reset() { + *x = PingResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_ping_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PingResponse) ProtoMessage() {} + +func (x *PingResponse) ProtoReflect() protoreflect.Message { + mi := &file_ping_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. +func (*PingResponse) Descriptor() ([]byte, []int) { + return file_ping_proto_rawDescGZIP(), []int{1} +} + +func (x *PingResponse) GetReceived() string { + if x != nil { + return x.Received + } + return "" +} + +var File_ping_proto protoreflect.FileDescriptor + +var file_ping_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, + 0x65, 0x6e, 0x74, 0x1a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x37, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, + 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2a, 0x0a, 0x0c, 0x50, 0x69, + 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x32, 0x42, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x2e, + 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_ping_proto_rawDescOnce sync.Once + file_ping_proto_rawDescData = file_ping_proto_rawDesc +) + +func file_ping_proto_rawDescGZIP() []byte { + file_ping_proto_rawDescOnce.Do(func() { + file_ping_proto_rawDescData = protoimpl.X.CompressGZIP(file_ping_proto_rawDescData) + }) + return file_ping_proto_rawDescData +} + +var file_ping_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_ping_proto_goTypes = []interface{}{ + (*PingRequest)(nil), // 0: agent.PingRequest + (*PingResponse)(nil), // 1: agent.PingResponse + (ConnectorType)(0), // 2: agent.ConnectorType +} +var file_ping_proto_depIdxs = []int32{ + 2, // 0: agent.PingRequest.type:type_name -> agent.ConnectorType + 0, // 1: agent.PingService.Ping:input_type -> agent.PingRequest + 1, // 2: agent.PingService.Ping:output_type -> agent.PingResponse + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_ping_proto_init() } +func file_ping_proto_init() { + if File_ping_proto != nil { + return + } + file_common_proto_init() + if !protoimpl.UnsafeEnabled { + file_ping_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ping_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PingResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_ping_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_ping_proto_goTypes, + DependencyIndexes: file_ping_proto_depIdxs, + MessageInfos: file_ping_proto_msgTypes, + }.Build() + File_ping_proto = out.File + file_ping_proto_rawDesc = nil + file_ping_proto_goTypes = nil + file_ping_proto_depIdxs = nil +} diff --git a/as400/agent/ping_grpc.pb.go b/as400/agent/ping_grpc.pb.go new file mode 100644 index 000000000..f283dc80a --- /dev/null +++ b/as400/agent/ping_grpc.pb.go @@ -0,0 +1,114 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.21.12 +// source: ping.proto + +package agent + +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 ( + PingService_Ping_FullMethodName = "/agent.PingService/Ping" +) + +// PingServiceClient is the client API for PingService 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 PingServiceClient interface { + Ping(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[PingRequest, PingResponse], error) +} + +type pingServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPingServiceClient(cc grpc.ClientConnInterface) PingServiceClient { + return &pingServiceClient{cc} +} + +func (c *pingServiceClient) Ping(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[PingRequest, PingResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &PingService_ServiceDesc.Streams[0], PingService_Ping_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[PingRequest, PingResponse]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type PingService_PingClient = grpc.ClientStreamingClient[PingRequest, PingResponse] + +// PingServiceServer is the server API for PingService service. +// All implementations must embed UnimplementedPingServiceServer +// for forward compatibility. +type PingServiceServer interface { + Ping(grpc.ClientStreamingServer[PingRequest, PingResponse]) error + mustEmbedUnimplementedPingServiceServer() +} + +// UnimplementedPingServiceServer must 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 UnimplementedPingServiceServer struct{} + +func (UnimplementedPingServiceServer) Ping(grpc.ClientStreamingServer[PingRequest, PingResponse]) error { + return status.Errorf(codes.Unimplemented, "method Ping not implemented") +} +func (UnimplementedPingServiceServer) mustEmbedUnimplementedPingServiceServer() {} +func (UnimplementedPingServiceServer) testEmbeddedByValue() {} + +// UnsafePingServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PingServiceServer will +// result in compilation errors. +type UnsafePingServiceServer interface { + mustEmbedUnimplementedPingServiceServer() +} + +func RegisterPingServiceServer(s grpc.ServiceRegistrar, srv PingServiceServer) { + // If the following call pancis, it indicates UnimplementedPingServiceServer 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(&PingService_ServiceDesc, srv) +} + +func _PingService_Ping_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(PingServiceServer).Ping(&grpc.GenericServerStream[PingRequest, PingResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type PingService_PingServer = grpc.ClientStreamingServer[PingRequest, PingResponse] + +// PingService_ServiceDesc is the grpc.ServiceDesc for PingService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PingService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "agent.PingService", + HandlerType: (*PingServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "Ping", + Handler: _PingService_Ping_Handler, + ClientStreams: true, + }, + }, + Metadata: "ping.proto", +} diff --git a/as400/agent/ping_imp.go b/as400/agent/ping_imp.go new file mode 100644 index 000000000..44cca9b3b --- /dev/null +++ b/as400/agent/ping_imp.go @@ -0,0 +1,90 @@ +package agent + +import ( + "context" + "strings" + "time" + + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/conn" + "github.com/utmstack/UTMStack/as400/utils" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + timeToSleep = 10 * time.Second + pingInterval = 15 * time.Second +) + +func StartPing(cnf *config.Config, ctx context.Context) { + var connErrMsgWritten, errorLogged bool + + for { + connection, err := conn.GetAgentManagerConnection(cnf) + if err != nil { + if !connErrMsgWritten { + utils.Logger.ErrorF("error connecting to Agent Manager: %v", err) + connErrMsgWritten = true + } else { + utils.Logger.LogF(100, "error connecting to Agent Manager: %v", err) + } + time.Sleep(timeToSleep) + continue + } + + client := NewPingServiceClient(connection) + stream, err := client.Ping(ctx) + if err != nil { + if !connErrMsgWritten { + utils.Logger.ErrorF("failed to start Ping Stream: %v", err) + connErrMsgWritten = true + } else { + utils.Logger.LogF(100, "failed to start Ping Stream: %v", err) + } + time.Sleep(timeToSleep) + continue + } + + utils.Logger.LogF(100, "Ping Stream started") + connErrMsgWritten = false + + ticker := time.NewTicker(pingInterval) + + for range ticker.C { + err := stream.Send(&PingRequest{Type: ConnectorType_AGENT}) + if err != nil { + if strings.Contains(err.Error(), "EOF") { + utils.Logger.LogF(100, "error sending Ping request: %v", err) + time.Sleep(timeToSleep) + break + } + st, ok := status.FromError(err) + if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { + if !errorLogged { + utils.Logger.ErrorF("error sending Ping request: %v", err) + errorLogged = true + } else { + utils.Logger.LogF(100, "error sending Ping request: %v", err) + } + time.Sleep(timeToSleep) + break + } else { + if !errorLogged { + utils.Logger.ErrorF("error sending Ping request: %v", err) + errorLogged = true + } else { + utils.Logger.LogF(100, "error sending Ping request: %v", err) + } + time.Sleep(timeToSleep) + continue + } + } + + errorLogged = false + utils.Logger.LogF(100, "Ping request sent") + } + + ticker.Stop() + } +} diff --git a/as400/agent/register.go b/as400/agent/register.go new file mode 100644 index 000000000..2be2e4362 --- /dev/null +++ b/as400/agent/register.go @@ -0,0 +1,63 @@ +package agent + +import ( + "context" + + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/conn" + "github.com/utmstack/UTMStack/as400/models" + "github.com/utmstack/UTMStack/as400/utils" + "google.golang.org/grpc/metadata" +) + +func RegisterCollector(cnf *config.Config, UTMKey string) error { + connection, err := conn.GetAgentManagerConnection(cnf) + if err != nil { + return utils.Logger.ErrorF("error connecting to Agent Manager: %v", err) + } + + collectorClient := NewCollectorServiceClient(connection) + ctx, cancel := context.WithCancel(context.Background()) + ctx = metadata.AppendToOutgoingContext(ctx, "connection-key", UTMKey) + defer cancel() + + ip, err := utils.GetIPAddress() + if err != nil { + return utils.Logger.ErrorF("error getting ip address: %v", err) + } + + osInfo, err := utils.GetOsInfo() + if err != nil { + return utils.Logger.ErrorF("error getting os info: %v", err) + } + + version := models.Version{} + err = utils.ReadJson(config.VersionPath, &version) + if err != nil { + return utils.Logger.ErrorF("error reading version file: %v", err) + } + + request := &RegisterRequest{ + Ip: ip, + Hostname: osInfo.Hostname, + Version: version.Version, + Collector: CollectorModule_AS_400, + } + + utils.Logger.Info("Registering UTMStack AS400 Collector with Agent Manager...") + utils.Logger.Info("Collector Details: IP=%s, Hostname=%s, Version=%s, Module=%s", + ip, osInfo.Hostname, version.Version, CollectorModule_AS_400.String()) + + response, err := collectorClient.RegisterCollector(ctx, request) + if err != nil { + return utils.Logger.ErrorF("failed to register collector: %v", err) + } + + cnf.CollectorID = uint(response.Id) + cnf.CollectorKey = response.Key + + utils.Logger.Info("UTMStack AS400 Collector registered successfully") + utils.Logger.Info("Collector ID: %d", cnf.CollectorID) + + return nil +} diff --git a/as400/agent/uninstall.go b/as400/agent/uninstall.go new file mode 100644 index 000000000..7c1884e3f --- /dev/null +++ b/as400/agent/uninstall.go @@ -0,0 +1,17 @@ +package agent + +import ( + "fmt" + "path/filepath" + + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/utils" +) + +func UninstallAll() error { + err := utils.Execute(filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.ServiceLogFile, "")), utils.GetMyPath(), "uninstall") + if err != nil { + return utils.Logger.ErrorF("%v", err) + } + return nil +} diff --git a/as400/collector/as400.go b/as400/collector/as400.go new file mode 100644 index 000000000..da9eb51e1 --- /dev/null +++ b/as400/collector/as400.go @@ -0,0 +1,235 @@ +package collector + +import ( + "bufio" + "context" + "io" + "os" + "os/exec" + "sync" + "syscall" + "time" + + "github.com/threatwinds/go-sdk/entities" + "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/logservice" + "github.com/utmstack/UTMStack/as400/utils" +) + +type AS400Collector struct { + configStreamManager *ConfigStreamManager + collectorJarPath string + configFilePath string + currentProcess *exec.Cmd + processMutex sync.Mutex + isRunning bool + ctx context.Context + cancel context.CancelFunc + hostname string +} + +func NewAS400Collector() *AS400Collector { + hostname, err := os.Hostname() + if err != nil { + utils.Logger.ErrorF("error getting hostname: %v", err) + hostname = "unknown" + } + + collector := &AS400Collector{ + collectorJarPath: config.CollectorJarPath, + configFilePath: config.ConfigFilePath, + hostname: hostname, + } + + collector.configStreamManager = NewConfigStreamManager(collector.handleConfigurationChange) + + return collector +} + +func (c *AS400Collector) Start(ctx context.Context, cnf *config.Config) error { + utils.Logger.Info("Starting AS400 Collector...") + + c.ctx, c.cancel = context.WithCancel(ctx) + + if !utils.CheckIfPathExist(c.collectorJarPath) { + return utils.Logger.ErrorF("AS400 collector JAR not found at: %s", c.collectorJarPath) + } + + if utils.CheckIfPathExist(c.configFilePath) { + utils.Logger.Info("Found existing configuration file, starting JAR...") + if err := c.startCollectorProcess(); err != nil { + utils.Logger.ErrorF("Error starting JAR with existing config: %v", err) + } + } + + go c.configStreamManager.Start(cnf, c.ctx) + + utils.Logger.Info("AS400 started, waiting for configuration...") + + return nil +} + +func (c *AS400Collector) Stop() error { + utils.Logger.Info("Stopping AS400 Collector...") + + if c.cancel != nil { + c.cancel() + } + + if err := c.stopCollectorProcess(); err != nil { + utils.Logger.ErrorF("Error stopping collector process: %v", err) + } + + utils.Logger.Info("AS400 Collector stopped") + return nil +} + +func (c *AS400Collector) handleConfigurationChange(newConfig *AS400CollectorConfig) { + if err := c.saveConfig(newConfig); err != nil { + utils.Logger.ErrorF("Error saving configuration: %v", err) + return + } + + if !c.isRunning { + if err := c.startCollectorProcess(); err != nil { + utils.Logger.ErrorF("Error starting JAR: %v", err) + } + } +} + +func (c *AS400Collector) saveConfig(config *AS400CollectorConfig) error { + if err := EncryptPasswords(config); err != nil { + return utils.Logger.ErrorF("error encrypting passwords: %v", err) + } + + if err := utils.WriteJSON(c.configFilePath, config); err != nil { + return utils.Logger.ErrorF("error writing config file: %v", err) + } + + utils.Logger.Info("Configuration saved: %d servers", len(config.Servers)) + return nil +} + +func (c *AS400Collector) startCollectorProcess() error { + c.processMutex.Lock() + defer c.processMutex.Unlock() + + if c.isRunning { + return nil + } + + utils.Logger.Info("Starting AS400 collector JAR...") + + cmd := exec.CommandContext(c.ctx, "java", "-jar", c.collectorJarPath, "RUN") + cmd.Dir = utils.GetMyPath() + cmd.Env = append(os.Environ(), "AS400_SECRET="+config.REPLACE_KEY) + + stdout, err := cmd.StdoutPipe() + if err != nil { + return utils.Logger.ErrorF("error creating stdout pipe: %v", err) + } + + stderr, err := cmd.StderrPipe() + if err != nil { + return utils.Logger.ErrorF("error creating stderr pipe: %v", err) + } + + if err := cmd.Start(); err != nil { + return utils.Logger.ErrorF("error starting JAR: %v", err) + } + + c.currentProcess = cmd + c.isRunning = true + + utils.Logger.Info("JAR started (PID: %d)", cmd.Process.Pid) + + go c.processCollectorLogs(stdout) + go c.processCollectorErrors(stderr) + go c.monitorProcess(cmd) + + return nil +} + +func (c *AS400Collector) processCollectorLogs(stdout io.ReadCloser) { + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + logLine := scanner.Text() + + validatedLog, _, err := entities.ValidateString(logLine, false) + if err != nil { + utils.Logger.ErrorF("invalid log: %v", err) + continue + } + + logservice.LogQueue <- &plugins.Log{ + DataType: string(config.DataType), + DataSource: c.hostname, + Raw: validatedLog, + } + } + + if err := scanner.Err(); err != nil { + utils.Logger.ErrorF("error reading stdout: %v", err) + } +} + +func (c *AS400Collector) processCollectorErrors(stderr io.ReadCloser) { + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + utils.Logger.ErrorF("JAR error: %s", scanner.Text()) + } + + if err := scanner.Err(); err != nil { + utils.Logger.ErrorF("error reading stderr: %v", err) + } +} + +func (c *AS400Collector) stopCollectorProcess() error { + c.processMutex.Lock() + defer c.processMutex.Unlock() + + if !c.isRunning || c.currentProcess == nil { + return nil + } + + utils.Logger.Info("Stopping JAR (PID: %d)...", c.currentProcess.Process.Pid) + + if err := c.currentProcess.Process.Signal(syscall.SIGTERM); err != nil { + utils.Logger.ErrorF("error sending SIGTERM: %v", err) + } + + done := make(chan error, 1) + go func() { + done <- c.currentProcess.Wait() + }() + + select { + case <-time.After(10 * time.Second): + utils.Logger.Info("Forcing SIGKILL...") + c.currentProcess.Process.Kill() + <-done + case <-done: + } + + c.isRunning = false + c.currentProcess = nil + + utils.Logger.Info("JAR stopped") + return nil +} + +func (c *AS400Collector) monitorProcess(cmd *exec.Cmd) { + err := cmd.Wait() + + c.processMutex.Lock() + c.isRunning = false + c.currentProcess = nil + c.processMutex.Unlock() + + if err != nil { + utils.Logger.ErrorF("JAR exited with error: %v", err) + } else { + utils.Logger.Info("JAR exited") + } +} diff --git a/as400/collector/config.go b/as400/collector/config.go new file mode 100644 index 000000000..b57eb56f3 --- /dev/null +++ b/as400/collector/config.go @@ -0,0 +1,214 @@ +package collector + +import ( + "context" + "strings" + "time" + + aesCrypt "github.com/AtlasInsideCorp/AtlasInsideAES" + pb "github.com/utmstack/UTMStack/as400/agent" + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/conn" + "github.com/utmstack/UTMStack/as400/utils" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type ConfigStreamManager struct { + currentConfig *AS400CollectorConfig + onConfigChange func(*AS400CollectorConfig) +} + +type AS400CollectorConfig struct { + Servers []AS400ServerConfig `json:"servers"` +} + +type AS400ServerConfig struct { + Tenant string `json:"tenant"` + Hostname string `json:"hostname"` + UserId string `json:"userId"` + Password string `json:"password"` +} + +func NewConfigStreamManager(onConfigChange func(*AS400CollectorConfig)) *ConfigStreamManager { + return &ConfigStreamManager{ + onConfigChange: onConfigChange, + } +} + +func (csm *ConfigStreamManager) Start(cnf *config.Config, ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + default: + connection, err := conn.GetAgentManagerConnection(cnf) + if err != nil { + utils.Logger.ErrorF("error connecting to backend: %v", err) + time.Sleep(10 * time.Second) + continue + } + + stream, err := pb.NewCollectorServiceClient(connection).CollectorStream(ctx) + if err != nil { + utils.Logger.ErrorF("error opening stream: %v", err) + time.Sleep(10 * time.Second) + continue + } + + utils.Logger.Info("Config stream connected") + csm.handleStream(stream) + time.Sleep(5 * time.Second) + } + } +} + +func (csm *ConfigStreamManager) handleStream(stream pb.CollectorService_CollectorStreamClient) { + for { + msg, err := stream.Recv() + if err != nil { + if strings.Contains(err.Error(), "EOF") { + return + } + if st, ok := status.FromError(err); ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { + return + } + utils.Logger.ErrorF("stream error: %v", err) + return + } + + if protoConfig := msg.GetConfig(); protoConfig != nil { + csm.processConfiguration(stream, protoConfig) + } + } +} + +func (csm *ConfigStreamManager) processConfiguration(stream pb.CollectorService_CollectorStreamClient, protoConfig *pb.CollectorConfig) { + requestID := protoConfig.GetRequestId() + + as400Config, err := csm.protoToConfig(protoConfig) + if err != nil { + utils.Logger.ErrorF("invalid config: %v", err) + csm.sendAcknowledgment(stream, requestID, false) + return + } + + if err := csm.validateConfig(as400Config); err != nil { + utils.Logger.ErrorF("validation failed: %v", err) + csm.sendAcknowledgment(stream, requestID, false) + return + } + + if csm.currentConfig != nil && csm.configEquals(csm.currentConfig, as400Config) { + csm.sendAcknowledgment(stream, requestID, true) + return + } + + csm.currentConfig = as400Config + utils.Logger.Info("Config received: %d servers", len(as400Config.Servers)) + + if csm.onConfigChange != nil { + csm.onConfigChange(as400Config) + } + + csm.sendAcknowledgment(stream, requestID, true) +} + +func (csm *ConfigStreamManager) protoToConfig(protoConfig *pb.CollectorConfig) (*AS400CollectorConfig, error) { + config := &AS400CollectorConfig{ + Servers: make([]AS400ServerConfig, 0), + } + + for _, group := range protoConfig.GetGroups() { + server := AS400ServerConfig{ + Tenant: group.GetGroupName(), + } + + utils.Logger.Info("Processing group: %s", server.Tenant) + + confs := group.GetConfigurations() + utils.Logger.Info(" Configurations count: %d", len(confs)) + + for _, conf := range confs { + key := conf.GetConfKey() + value := conf.GetConfValue() + + switch key { + case "hostname", "collector.as400.hostname": + server.Hostname = value + case "userId", "collector.as400.user": + server.UserId = value + case "password", "collector.as400.password": + server.Password = value + default: + utils.Logger.Info(" WARNING: Unknown config key '%s' (ignored)", key) + } + } + + config.Servers = append(config.Servers, server) + } + + return config, nil +} + +func (csm *ConfigStreamManager) validateConfig(config *AS400CollectorConfig) error { + if len(config.Servers) == 0 { + return utils.Logger.ErrorF("no servers configured") + } + + for i, s := range config.Servers { + if s.Tenant == "" || s.Hostname == "" || s.UserId == "" || s.Password == "" { + return utils.Logger.ErrorF("server %d (%s): missing required fields", i, s.Tenant) + } + } + return nil +} + +func (csm *ConfigStreamManager) configEquals(a, b *AS400CollectorConfig) bool { + if len(a.Servers) != len(b.Servers) { + return false + } + + for i := range a.Servers { + if a.Servers[i].Tenant != b.Servers[i].Tenant || + a.Servers[i].Hostname != b.Servers[i].Hostname || + a.Servers[i].UserId != b.Servers[i].UserId || + a.Servers[i].Password != b.Servers[i].Password { + return false + } + } + + return true +} + +func (csm *ConfigStreamManager) sendAcknowledgment(stream pb.CollectorService_CollectorStreamClient, requestId string, accepted bool) { + acceptedStr := "false" + if accepted { + acceptedStr = "true" + } + + ack := &pb.CollectorMessages{ + StreamMessage: &pb.CollectorMessages_Result{ + Result: &pb.ConfigKnowledge{ + Accepted: acceptedStr, + RequestId: requestId, + }, + }, + } + + if err := stream.Send(ack); err != nil { + utils.Logger.ErrorF("ack send failed: %v", err) + } +} + +func EncryptPasswords(cfg *AS400CollectorConfig) error { + for i := range cfg.Servers { + encPassword, err := aesCrypt.AESEncrypt(cfg.Servers[i].Password, []byte(config.REPLACE_KEY)) + if err != nil { + return err + } + cfg.Servers[i].Password = encPassword + } + + return nil +} diff --git a/as400/config/config.go b/as400/config/config.go new file mode 100644 index 000000000..ba1ced37a --- /dev/null +++ b/as400/config/config.go @@ -0,0 +1,168 @@ +package config + +import ( + "os" + "sync" + + aesCrypt "github.com/AtlasInsideCorp/AtlasInsideAES" + "github.com/google/uuid" + "github.com/utmstack/UTMStack/as400/utils" +) + +type MSGDS struct { + DataSource string + Message string +} + +type InstallationUUID struct { + UUID string `yaml:"uuid"` +} + +type Config struct { + Server string `yaml:"server"` + CollectorID uint `yaml:"collector-id"` + CollectorKey string `yaml:"collector-key"` + SkipCertValidation bool `yaml:"insecure"` +} + +func GetInitialConfig() (*Config, string) { + cnf := Config{ + Server: os.Args[2], + } + skip := os.Args[4] + if skip == "yes" { + cnf.SkipCertValidation = true + } else { + cnf.SkipCertValidation = false + } + return &cnf, os.Args[3] +} + +var ( + cnf = Config{} + confOnce sync.Once + installationId = "" + installationIdOnce sync.Once +) + +func GetCurrentConfig() (*Config, error) { + var errR error + confOnce.Do(func() { + uuidExists := utils.CheckIfPathExist(UUIDFileName) + + var encryptConfig Config + if err := utils.ReadYAML(ConfigurationFile, &encryptConfig); err != nil { + errR = utils.Logger.ErrorF("error reading config file: %v", err) + return + } + + var key []byte + var err error + if uuidExists { + id, err := GetUUID() + if err != nil { + errR = utils.Logger.ErrorF("failed to get uuid: %v", err) + return + } + + key, err = utils.GenerateKeyByUUID(REPLACE_KEY, id) + if err != nil { + errR = utils.Logger.ErrorF("error geneating key: %v", err) + return + } + } else { + key, err = utils.GenerateKey(REPLACE_KEY) + if err != nil { + errR = utils.Logger.ErrorF("error geneating key: %v", err) + return + } + } + + collectorKey, err := aesCrypt.AESDecrypt(encryptConfig.CollectorKey, key) + if err != nil { + errR = utils.Logger.ErrorF("error encoding collector key: %v", err) + return + } + + cnf.Server = encryptConfig.Server + cnf.CollectorID = encryptConfig.CollectorID + cnf.CollectorKey = collectorKey + cnf.SkipCertValidation = encryptConfig.SkipCertValidation + + if !uuidExists { + if err := SaveConfig(&cnf); err != nil { + errR = utils.Logger.ErrorF("error writing config file: %v", err) + return + } + } + }) + if errR != nil { + return nil, errR + } + return &cnf, nil +} + +func SaveConfig(cnf *Config) error { + id, err := GenerateNewUUID() + if err != nil { + return utils.Logger.ErrorF("failed to generate uuid: %v", err) + } + + key, err := utils.GenerateKeyByUUID(REPLACE_KEY, id) + if err != nil { + return utils.Logger.ErrorF("error geneating key: %v", err) + } + + collectorKey, err := aesCrypt.AESEncrypt(cnf.CollectorKey, key) + if err != nil { + return utils.Logger.ErrorF("error encoding agent key: %v", err) + } + + encryptConf := &Config{ + Server: cnf.Server, + CollectorID: cnf.CollectorID, + CollectorKey: collectorKey, + SkipCertValidation: cnf.SkipCertValidation, + } + + if err := utils.WriteYAML(ConfigurationFile, encryptConf); err != nil { + return err + } + return nil +} + +func GenerateNewUUID() (string, error) { + id, err := uuid.NewRandom() + if err != nil { + return "", utils.Logger.ErrorF("failed to generate uuid: %v", err) + } + + InstallationUUID := InstallationUUID{ + UUID: id.String(), + } + + if err = utils.WriteYAML(UUIDFileName, InstallationUUID); err != nil { + return "", utils.Logger.ErrorF("error writing uuid file: %v", err) + } + + return InstallationUUID.UUID, nil +} + +func GetUUID() (string, error) { + var errR error + installationIdOnce.Do(func() { + var id = InstallationUUID{} + if err := utils.ReadYAML(UUIDFileName, &id); err != nil { + errR = utils.Logger.ErrorF("error reading uuid file: %v", err) + return + } + + installationId = id.UUID + }) + + if errR != nil { + return "", errR + } + + return installationId, nil +} diff --git a/as400/config/const.go b/as400/config/const.go new file mode 100644 index 000000000..db4762522 --- /dev/null +++ b/as400/config/const.go @@ -0,0 +1,28 @@ +package config + +import ( + "path/filepath" + + "github.com/utmstack/UTMStack/as400/utils" +) + +const ( + REPLACE_KEY string = "" + DataType string = "ibm-as400" +) + +var ( + DependUrl = "https://%s:%s/private/dependencies/collector/as400/%s" + AgentManagerPort = "9000" + LogAuthProxyPort = "50051" + DependenciesPort = "9001" + + ServiceLogFile = filepath.Join(utils.GetMyPath(), "logs", "utmstack_as400_collector.log") + UUIDFileName = filepath.Join(utils.GetMyPath(), "uuid.yml") + ConfigurationFile = filepath.Join(utils.GetMyPath(), "config.yml") + RetentionConfigFile = filepath.Join(utils.GetMyPath(), "retention.json") + LogsDBFile = filepath.Join(utils.GetMyPath(), "logs_process", "logs.db") + VersionPath = filepath.Join(utils.GetMyPath(), "version.json") + CollectorJarPath = filepath.Join(utils.GetMyPath(), "as400-collector.jar") + ConfigFilePath = filepath.Join(utils.GetMyPath(), "local_storage", "server.json") +) diff --git a/as400/conn/conn.go b/as400/conn/conn.go new file mode 100644 index 000000000..6d6104cc4 --- /dev/null +++ b/as400/conn/conn.go @@ -0,0 +1,106 @@ +package conn + +import ( + "crypto/tls" + "sync" + "time" + + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/utils" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials" +) + +const ( + maxMessageSize = 1024 * 1024 * 1024 + maxConnectionAttempts = 3 + initialReconnectDelay = 10 * time.Second + maxReconnectDelay = 60 * time.Second +) + +var ( + correlationConn *grpc.ClientConn + correlationConnOnce sync.Once + agentManagerConn *grpc.ClientConn + agentManagerConnOnce sync.Once +) + +func GetAgentManagerConnection(cnf *config.Config) (*grpc.ClientConn, error) { + var err error + agentManagerConnOnce.Do(func() { + agentManagerConn, err = connectToServer(cnf.Server, config.AgentManagerPort, cnf.SkipCertValidation) + if err != nil { + err = utils.Logger.ErrorF("error connecting to Agent Manager: %v", err) + } + }) + if err != nil { + return nil, err + } + + state := agentManagerConn.GetState() + if state == connectivity.Shutdown || state == connectivity.TransientFailure { + agentManagerConn.Close() + agentManagerConn, err = connectToServer(cnf.Server, config.AgentManagerPort, cnf.SkipCertValidation) + if err != nil { + return nil, utils.Logger.ErrorF("error connecting to Agent Manager: %v", err) + } + } + + return agentManagerConn, nil +} + +func GetCorrelationConnection(cnf *config.Config) (*grpc.ClientConn, error) { + var err error + correlationConnOnce.Do(func() { + correlationConn, err = connectToServer(cnf.Server, config.LogAuthProxyPort, cnf.SkipCertValidation) + if err != nil { + err = utils.Logger.ErrorF("error connecting to Correlation: %v", err) + } + }) + if err != nil { + return nil, err + } + + state := correlationConn.GetState() + if state == connectivity.Shutdown || state == connectivity.TransientFailure { + correlationConn.Close() + correlationConn, err = connectToServer(cnf.Server, config.LogAuthProxyPort, cnf.SkipCertValidation) + if err != nil { + return nil, utils.Logger.ErrorF("error connecting to Correlation: %v", err) + } + } + + return correlationConn, nil +} + +func connectToServer(addrs, port string, skip bool) (*grpc.ClientConn, error) { + connectionAttemps := 0 + reconnectDelay := initialReconnectDelay + + serverAddress := addrs + ":" + port + var conn *grpc.ClientConn + var err error + + for { + if connectionAttemps >= maxConnectionAttempts { + return nil, utils.Logger.ErrorF("failed to connect to Server: %v", err) + } + + conn, err = grpc.NewClient( + serverAddress, + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMessageSize)), + grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: skip}))) + if err != nil { + connectionAttemps++ + utils.Logger.ErrorF("error connecting to Server, trying again in %.0f seconds", reconnectDelay.Seconds()) + time.Sleep(reconnectDelay) + reconnectDelay = utils.IncrementReconnectDelay(reconnectDelay, maxReconnectDelay) + continue + } + + break + } + + return conn, nil +} diff --git a/as400/database/db.go b/as400/database/db.go new file mode 100644 index 000000000..c85b06379 --- /dev/null +++ b/as400/database/db.go @@ -0,0 +1,129 @@ +package database + +import ( + "errors" + "fmt" + "log" + "os" + "path/filepath" + "sync" + + "github.com/glebarez/sqlite" + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/utils" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var ( + dbInstance *Database + dbOnce sync.Once +) + +type Database struct { + db *gorm.DB + locker sync.RWMutex +} + +func (d *Database) Migrate(data interface{}) error { + return d.db.AutoMigrate(data) +} + +func (d *Database) Create(data interface{}) error { + return d.db.Create(data).Error +} + +func (d *Database) Find(data interface{}, field string, value interface{}) (bool, error) { + err := d.db.Where(fmt.Sprintf("%v = ?", field), value).Find(data).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return false, nil + } + return false, err + } + return true, nil +} + +func (d *Database) GetAll(data interface{}) error { + if err := d.db.Find(data).Error; err != nil { + return err + } + return nil +} + +func (d *Database) Update(data interface{}, searchField string, searchValue string, modifyField string, newValue interface{}) error { + return d.db.Model(data).Where(fmt.Sprintf("%v = ?", searchField), searchValue).Update(modifyField, newValue).Error +} + +func (d *Database) Delete(data interface{}, field string, value string) error { + return d.db.Where(fmt.Sprintf("%v = ?", field), value).Delete(data).Error +} + +func (d *Database) DeleteOld(data interface{}, retentionMegabytes int) (int, error) { + currentSize, err := GetDatabaseSizeInMB() + if err != nil { + return 0, utils.Logger.ErrorF("error getting database size: %v", err) + } + + var rowsAffected int + for currentSize > retentionMegabytes { + result := d.db.Where("1 = 1").Order("created_at ASC").Limit(10).Delete(data) + if result.Error != nil { + return rowsAffected, result.Error + } + rowsAffected += int(result.RowsAffected) + d.db.Exec("VACUUM;") + currentSize, err = GetDatabaseSizeInMB() + if err != nil { + return rowsAffected, utils.Logger.ErrorF("error getting database size: %v", err) + } + } + + return rowsAffected, nil +} + +func (d *Database) Lock() { + d.locker.Lock() +} + +func (d *Database) Unlock() { + d.locker.Unlock() +} + +func GetDB() *Database { + dbOnce.Do(func() { + path := filepath.Join(utils.GetMyPath(), "logs_process") + err := utils.CreatePathIfNotExist(path) + if err != nil { + log.Fatalf("error creating database path: %v", err) + } + path = config.LogsDBFile + if _, err := os.Stat(path); os.IsNotExist(err) { + file, err := os.Create(path) + if err != nil { + log.Fatalf("error creating database file: %v", err) + } + file.Close() + } + + conn, err := gorm.Open(sqlite.Open(path), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + }) + if err != nil { + log.Fatalf("error connecting with database: %v", err) + } + + dbInstance = &Database{db: conn} + + }) + + return dbInstance +} + +func GetDatabaseSizeInMB() (int, error) { + fileInfo, err := os.Stat(config.LogsDBFile) + if err != nil { + return 0, err + } + return int(fileInfo.Size() / (1024 * 1024)), nil +} diff --git a/as400/go.mod b/as400/go.mod new file mode 100644 index 000000000..920bc5e32 --- /dev/null +++ b/as400/go.mod @@ -0,0 +1,71 @@ +module github.com/utmstack/UTMStack/as400 + +go 1.25.0 + +require ( + github.com/AtlasInsideCorp/AtlasInsideAES v1.0.0 + github.com/elastic/go-sysinfo v1.15.4 + github.com/glebarez/sqlite v1.11.0 + github.com/google/uuid v1.6.0 + github.com/kardianos/service v1.2.4 + github.com/threatwinds/go-sdk v1.0.45 + github.com/threatwinds/logger v1.2.2 + google.golang.org/grpc v1.75.1 + google.golang.org/protobuf v1.36.9 + gopkg.in/yaml.v2 v2.4.0 + gorm.io/gorm v1.31.0 +) + +require ( + cel.dev/expr v0.24.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/elastic/go-windows v1.0.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/gin-gonic/gin v1.10.1 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/google/cel-go v0.26.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/stoewer/go-strcase v1.3.1 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/arch v0.19.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.23.1 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect +) diff --git a/as400/go.sum b/as400/go.sum new file mode 100644 index 000000000..a8826419f --- /dev/null +++ b/as400/go.sum @@ -0,0 +1,194 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +github.com/AtlasInsideCorp/AtlasInsideAES v1.0.0 h1:TBiBl9KCa4i4epY0/q9WSC4ugavL6+6JUkOXWDnMM6I= +github.com/AtlasInsideCorp/AtlasInsideAES v1.0.0/go.mod h1:cRhQ3TS/VEfu/z+qaciyuDZdtxgaXgaX8+G6Wa5NzBk= +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/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/elastic/go-sysinfo v1.15.4 h1:A3zQcunCxik14MgXu39cXFXcIw2sFXZ0zL886eyiv1Q= +github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZPk3G244lHrU= +github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI= +github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +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/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-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +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/kardianos/service v1.2.4 h1:XNlGtZOYNx2u91urOdg/Kfmc+gfmuIo1Dd3rEi2OgBk= +github.com/kardianos/service v1.2.4/go.mod h1:E4V9ufUuY82F7Ztlu1eN9VXWIQxg8NoLQlmFe0MtrXc= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +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/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +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/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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +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/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= +github.com/stoewer/go-strcase v1.3.1/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= +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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/threatwinds/go-sdk v1.0.45 h1:KZ3s3HviNRrOkg5EqjFnoauANFFzTqjNFyshPLY2SoI= +github.com/threatwinds/go-sdk v1.0.45/go.mod h1:tcWn6r6vqID/W/nL3UKfc5NafA3V/cSkiLvfJnwB58c= +github.com/threatwinds/logger v1.2.2 h1:sVuT8yhbecPqP4tT8EwHfp1czNC6e1wdkE1ihNnuBdA= +github.com/threatwinds/logger v1.2.2/go.mod h1:Amq0QI1y7fkTpnBUgeGVu2Z/C4u4ys2pNLUOuj3UAAU= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU= +golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 h1:mVXdvnmR3S3BQOqHECm9NGMjYiRtEvDYcqAqedTXY6s= +google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:vYFwMYFbmA8vl6Z/krj/h7+U/AqpHknwJX4Uqgfyc7I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 h1:qJW29YvkiJmXOYMu5Tf8lyrTp3dOS+K4z6IixtLaCf8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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= +gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= +gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= +modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/as400/logservice/processor.go b/as400/logservice/processor.go new file mode 100644 index 000000000..ac7799c75 --- /dev/null +++ b/as400/logservice/processor.go @@ -0,0 +1,268 @@ +package logservice + +import ( + "context" + "errors" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/google/uuid" + "github.com/threatwinds/go-sdk/plugins" + + "github.com/utmstack/UTMStack/as400/agent" + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/conn" + "github.com/utmstack/UTMStack/as400/database" + "github.com/utmstack/UTMStack/as400/models" + "github.com/utmstack/UTMStack/as400/utils" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +type LogProcessor struct { + db *database.Database + connErrWritten bool + ackErrWritten bool + sendErrWritten bool +} + +var ( + processor LogProcessor + processorOnce sync.Once + LogQueue = make(chan *plugins.Log) + timeToSleep = 10 * time.Second + timeCLeanLogs = 10 * time.Minute +) + +func GetLogProcessor() LogProcessor { + processorOnce.Do(func() { + processor = LogProcessor{ + db: database.GetDB(), + connErrWritten: false, + ackErrWritten: false, + sendErrWritten: false, + } + }) + return processor +} + +func (l *LogProcessor) ProcessLogs(cnf *config.Config, ctx context.Context) { + go l.CleanCountedLogs() + + for { + ctxEof, cancelEof := context.WithCancel(context.Background()) + connection, err := conn.GetCorrelationConnection(cnf) + if err != nil { + if !l.connErrWritten { + utils.Logger.ErrorF("error connecting to Correlation: %v", err) + l.connErrWritten = true + } + time.Sleep(10 * time.Second) + continue + } + + client := plugins.NewIntegrationClient(connection) + plClient := createClient(client, ctx, cnf) + l.connErrWritten = false + + go l.handleAcknowledgements(plClient, ctxEof, cancelEof) + l.processLogs(plClient, ctxEof, cancelEof) + } +} + +func (l *LogProcessor) handleAcknowledgements(plClient plugins.Integration_ProcessLogClient, ctx context.Context, cancel context.CancelFunc) { + for { + select { + case <-ctx.Done(): + return + default: + ack, err := plClient.Recv() + if err != nil { + if strings.Contains(err.Error(), "EOF") { + time.Sleep(timeToSleep) + cancel() + return + } + st, ok := status.FromError(err) + if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { + if !l.ackErrWritten { + utils.Logger.ErrorF("failed to receive ack: %v", err) + l.ackErrWritten = true + } + time.Sleep(timeToSleep) + cancel() + return + } else { + if !l.ackErrWritten { + utils.Logger.ErrorF("failed to receive ack: %v", err) + l.ackErrWritten = true + } + time.Sleep(timeToSleep) + continue + } + } + + l.ackErrWritten = false + + l.db.Lock() + err = l.db.Update(&models.Log{}, "id", ack.LastId, "processed", true) + if err != nil { + utils.Logger.ErrorF("failed to update log: %v", err) + } + l.db.Unlock() + } + } +} + +func (l *LogProcessor) processLogs(plClient plugins.Integration_ProcessLogClient, ctx context.Context, cancel context.CancelFunc) { + for { + select { + case <-ctx.Done(): + utils.Logger.Info("context done, exiting processLogs") + return + case newLog := <-LogQueue: + id, err := uuid.NewRandom() + if err != nil { + utils.Logger.ErrorF("failed to generate uuid: %v", err) + continue + } + + newLog.Id = id.String() + l.db.Lock() + err = l.db.Create(&models.Log{ID: newLog.Id, Log: newLog.Raw, Type: newLog.DataType, CreatedAt: time.Now(), DataSource: newLog.DataSource, Processed: false}) + if err != nil { + utils.Logger.ErrorF("failed to save log: %v :log: %s", err, newLog.Raw) + } + l.db.Unlock() + + err = plClient.Send(newLog) + if err != nil { + if strings.Contains(err.Error(), "EOF") { + time.Sleep(timeToSleep) + cancel() + return + } + st, ok := status.FromError(err) + if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { + if !l.sendErrWritten { + utils.Logger.ErrorF("failed to send log: %v :log: %s", err, newLog.Raw) + l.sendErrWritten = true + } + time.Sleep(timeToSleep) + cancel() + return + } else { + if !l.sendErrWritten { + utils.Logger.ErrorF("failed to send log: %v :log: %s", err, newLog.Raw) + l.sendErrWritten = true + } + time.Sleep(timeToSleep) + continue + } + } + l.sendErrWritten = false + } + } +} + +func (l *LogProcessor) CleanCountedLogs() { + ticker := time.NewTicker(timeCLeanLogs) + defer ticker.Stop() + for range ticker.C { + dataRetention, err := GetDataRetention() + if err != nil { + utils.Logger.ErrorF("error getting data retention: %s", err) + continue + } + l.db.Lock() + _, err = l.db.DeleteOld(&models.Log{}, dataRetention) + if err != nil { + utils.Logger.ErrorF("error deleting old logs: %s", err) + } + l.db.Unlock() + + unprocessed := make([]models.Log, 0, 10) + l.db.Lock() + found, err := l.db.Find(&unprocessed, "processed", false) + l.db.Unlock() + if err != nil { + utils.Logger.ErrorF("error finding unprocessed logs: %s", err) + continue + } + + if found { + for _, log := range unprocessed { + LogQueue <- &plugins.Log{ + Id: log.ID, + Raw: log.Log, + DataType: log.Type, + DataSource: log.DataSource, + Timestamp: log.CreatedAt.Format(time.RFC3339Nano), + } + } + } + } +} + +func createClient(client plugins.IntegrationClient, ctx context.Context, cnf *config.Config) plugins.Integration_ProcessLogClient { + var connErrMsgWritten bool + invalidKeyCounter := 0 + for { + authCtx := metadata.AppendToOutgoingContext(ctx, + "key", cnf.CollectorKey, + "id", strconv.Itoa(int(cnf.CollectorID)), + "type", "collector") + + plClient, err := client.ProcessLog(authCtx) + if err != nil { + if strings.Contains(err.Error(), "invalid agent key") { + invalidKeyCounter++ + if invalidKeyCounter >= 20 { + utils.Logger.Info("Uninstalling collector: reason: collector has been removed from the panel...") + _ = agent.UninstallAll() + os.Exit(1) + } + } else { + invalidKeyCounter = 0 + } + if !connErrMsgWritten { + utils.Logger.ErrorF("failed to create input client: %v", err) + connErrMsgWritten = true + } + time.Sleep(timeToSleep) + continue + } + return plClient + } +} + +func SetDataRetention(retention string) error { + if retention == "" { + retention = "20" + } + + retentionInt, err := strconv.Atoi(retention) + if err != nil { + return errors.New("retention must be a number (number of megabytes)") + } + + if retentionInt < 1 { + return errors.New("retention must be greater than 0") + } + + return utils.WriteJSON(config.RetentionConfigFile, models.DataRetention{Retention: retentionInt}) +} + +func GetDataRetention() (int, error) { + retention := models.DataRetention{} + err := utils.ReadJson(config.RetentionConfigFile, &retention) + if err != nil { + return 0, err + } + + return retention.Retention, nil +} diff --git a/as400/main.go b/as400/main.go new file mode 100644 index 000000000..6717783bc --- /dev/null +++ b/as400/main.go @@ -0,0 +1,205 @@ +package main + +import ( + "fmt" + "os" + "time" + + pb "github.com/utmstack/UTMStack/as400/agent" + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/database" + "github.com/utmstack/UTMStack/as400/logservice" + "github.com/utmstack/UTMStack/as400/models" + "github.com/utmstack/UTMStack/as400/serv" + "github.com/utmstack/UTMStack/as400/updates" + "github.com/utmstack/UTMStack/as400/utils" +) + +func main() { + utils.InitLogger(config.ServiceLogFile) + + if len(os.Args) > 1 { + arg := os.Args[1] + + isInstalled, err := utils.CheckIfServiceIsInstalled("UTMStackAS400Collector") + if err != nil { + fmt.Println("Error checking if service is installed: ", err) + os.Exit(1) + } + if arg != "install" && !isInstalled { + fmt.Println("UTMStackAS400Collector service is not installed") + os.Exit(1) + } else if arg == "install" && isInstalled { + fmt.Println("UTMStackAS400Collector service is already installed") + os.Exit(1) + } + + switch arg { + case "run": + serv.RunService() + case "install": + utils.PrintBanner() + fmt.Println("Installing UTMStackAS400Collector service ...") + + cnf, utmKey := config.GetInitialConfig() + + fmt.Print("Checking server connection ... ") + if err := utils.ArePortsReachable(cnf.Server, config.AgentManagerPort, config.LogAuthProxyPort, config.DependenciesPort); err != nil { + fmt.Println("\nError trying to connect to server: ", err) + os.Exit(1) + } + fmt.Println("[OK]") + + fmt.Print("Downloading dependencies ... ") + if err := updates.DownloadVersion(cnf.Server, cnf.SkipCertValidation); err != nil { + fmt.Println("\nError downloading version: ", err) + os.Exit(1) + } + if err := updates.DownloadJar(cnf.Server, cnf.SkipCertValidation); err != nil { + fmt.Println("\nError downloading jar: ", err) + os.Exit(1) + } + if err := updates.DownloadUpdater(cnf.Server, cnf.SkipCertValidation); err != nil { + fmt.Println("\nError downloading updater: ", err) + os.Exit(1) + } + fmt.Println("[OK]") + + fmt.Print("Installing Java ... ") + if err := utils.InstallJava(); err != nil { + fmt.Println("\nError installing java: ", err) + os.Exit(1) + } + fmt.Println("[OK]") + + fmt.Print("Configuring collector ... ") + err = pb.RegisterCollector(cnf, utmKey) + if err != nil { + fmt.Println("\nError registering collector: ", err) + os.Exit(1) + } + if err = config.SaveConfig(cnf); err != nil { + fmt.Println("\nError saving config: ", err) + os.Exit(1) + } + + if err := logservice.SetDataRetention(""); err != nil { + fmt.Println("\nError setting retention: ", err) + os.Exit(1) + } + fmt.Println("[OK]") + + fmt.Print(("Creating service ... ")) + serv.InstallService() + fmt.Println("[OK]") + + fmt.Print("Installing updater service ... ") + if err := utils.InstallUpdater(); err != nil { + fmt.Println("\nError installing updater: ", err) + os.Exit(1) + } + fmt.Println("[OK]") + + fmt.Println("UTMStackAS400Collector service installed correctly") + + case "change-retention": + fmt.Println("Changing log retention ...") + retention := os.Args[2] + + if err := logservice.SetDataRetention(retention); err != nil { + fmt.Println("Error trying to change retention: ", err) + os.Exit(1) + } + + fmt.Printf("Retention changed correctly to %s\n", retention) + time.Sleep(5 * time.Second) + + case "clean-logs": + fmt.Println("Cleaning old logs ...") + db := database.GetDB() + datR, err := logservice.GetDataRetention() + if err != nil { + fmt.Println("Error getting retention: ", err) + os.Exit(1) + } + _, err = db.DeleteOld(models.Log{}, datR) + if err != nil { + fmt.Println("Error cleaning logs: ", err) + os.Exit(1) + } + fmt.Println("Logs cleaned correctly") + time.Sleep(5 * time.Second) + + case "uninstall": + fmt.Println("Uninstalling UTMStackAS400Collector service ...") + + fmt.Print("Uninstalling updater service ... ") + if err := utils.UninstallUpdater(); err != nil { + fmt.Println("\nWarning uninstalling updater: ", err) + } else { + fmt.Println("[OK]") + } + + cnf, err := config.GetCurrentConfig() + if err != nil { + fmt.Println("Error getting config: ", err) + os.Exit(1) + } + if err = pb.DeleteAgent(cnf); err != nil { + utils.Logger.ErrorF("error deleting collector: %v", err) + } + + os.Remove(config.ConfigurationFile) + + serv.UninstallService() + + fmt.Println("Uninstalling java") + if err := utils.UninstallJava(); err != nil { + utils.Logger.ErrorF("error unistalling java: %v", err) + } + + fmt.Println("[OK]") + fmt.Println("UTMStackAS400Collector service uninstalled correctly") + os.Exit(1) + case "help": + Help() + default: + fmt.Println("unknown option") + } + } else { + serv.RunService() + } +} + +func Help() { + fmt.Println("### UTMStack Collector ###") + fmt.Println("Usage:") + fmt.Println(" To run the service: ./utmstack_as400_collector run") + fmt.Println(" To install the service: ./utmstack_as400_collector install") + fmt.Println(" To change log retention: ./utmstack_as400_collector change-retention ") + fmt.Println(" To clean old logs: ./utmstack_as400_collector clean-logs") + fmt.Println(" To uninstall the service: ./utmstack_as400_collector uninstall") + fmt.Println(" To debug UTMStack installation: ./utmstack_as400_collector debug-utmstack") + fmt.Println(" For help (this message): ./utmstack_as400_collector help") + fmt.Println() + fmt.Println("Options:") + fmt.Println(" run Run the UTMStackAS400Collector service") + fmt.Println(" install Install the UTMStackAS400Collector service") + fmt.Println(" change-retention Change the log retention to . Retention must be a number of megabytes. Example: 20") + fmt.Println(" clean-logs Clean old logs from the database") + fmt.Println(" uninstall Uninstall the UTMStackAS400Collector service") + fmt.Println(" debug-utmstack Debug UTMStack installation validation") + fmt.Println(" help Display this help message") + fmt.Println() + fmt.Println("Requirements:") + fmt.Println(" - UTMStack must be installed on this system") + fmt.Println(" - File /utmstack.yaml must exist in root directory") + fmt.Println(" - Directory /utmstack/ must exist") + fmt.Println() + fmt.Println("Note:") + fmt.Println(" - Make sure to run commands with appropriate permissions.") + fmt.Println(" - All commands require administrative privileges.") + fmt.Println(" - For detailed logs, check the service log file.") + fmt.Println() + os.Exit(0) +} diff --git a/as400/models/data.go b/as400/models/data.go new file mode 100644 index 000000000..051820585 --- /dev/null +++ b/as400/models/data.go @@ -0,0 +1,5 @@ +package models + +type DataRetention struct { + Retention int `json:"retention"` +} diff --git a/as400/models/schema.go b/as400/models/schema.go new file mode 100644 index 000000000..7e9fa46b7 --- /dev/null +++ b/as400/models/schema.go @@ -0,0 +1,14 @@ +package models + +import ( + "time" +) + +type Log struct { + ID string `gorm:"index"` + CreatedAt time.Time + DataSource string + Type string + Log string + Processed bool +} diff --git a/as400/models/version.go b/as400/models/version.go new file mode 100644 index 000000000..ad40993be --- /dev/null +++ b/as400/models/version.go @@ -0,0 +1,6 @@ +package models + +type Version struct { + Version string `json:"version"` + JarVersion string `json:"jar_version"` +} diff --git a/as400/serv/config.go b/as400/serv/config.go new file mode 100644 index 000000000..20cc8f75a --- /dev/null +++ b/as400/serv/config.go @@ -0,0 +1,17 @@ +package serv + +import ( + "github.com/kardianos/service" +) + +// GetConfigServ creates and returns a pointer to a service configuration structure. +func GetConfigServ() *service.Config { + svcConfig := &service.Config{ + Name: "UTMStackAS400Collector", + DisplayName: "UTMStack AS400 Collector", + Description: "UTMStack AS400 Collector Service", + Arguments: []string{"run"}, + } + + return svcConfig +} diff --git a/as400/serv/install.go b/as400/serv/install.go new file mode 100644 index 000000000..06ab38a06 --- /dev/null +++ b/as400/serv/install.go @@ -0,0 +1,29 @@ +package serv + +import ( + "fmt" + "os" + + "github.com/kardianos/service" +) + +func InstallService() { + svcConfig := GetConfigServ() + prg := new(program) + newService, err := service.New(prg, svcConfig) + if err != nil { + fmt.Println("\nError creating new service: ", err) + os.Exit(1) + } + err = newService.Install() + if err != nil { + fmt.Println("\nError installing new service: ", err) + os.Exit(1) + } + + err = newService.Start() + if err != nil { + fmt.Println("\nError starting new service: ", err) + os.Exit(1) + } +} diff --git a/as400/serv/run.go b/as400/serv/run.go new file mode 100644 index 000000000..782dcbf07 --- /dev/null +++ b/as400/serv/run.go @@ -0,0 +1,21 @@ +package serv + +import ( + "github.com/kardianos/service" + "github.com/utmstack/UTMStack/as400/utils" +) + +func RunService() { + svcConfig := GetConfigServ() + p := new(program) + + newService, err := service.New(p, svcConfig) + if err != nil { + utils.Logger.Fatal("error creating new service: %v", err) + } + + err = newService.Run() + if err != nil { + utils.Logger.Fatal("error running new service: %v", err) + } +} diff --git a/as400/serv/service.go b/as400/serv/service.go new file mode 100644 index 000000000..5c49326c8 --- /dev/null +++ b/as400/serv/service.go @@ -0,0 +1,72 @@ +package serv + +import ( + "context" + "os" + "os/signal" + "strconv" + "syscall" + + "github.com/kardianos/service" + + pb "github.com/utmstack/UTMStack/as400/agent" + collectors "github.com/utmstack/UTMStack/as400/collector" + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/database" + "github.com/utmstack/UTMStack/as400/logservice" + "github.com/utmstack/UTMStack/as400/models" + "github.com/utmstack/UTMStack/as400/utils" + "google.golang.org/grpc/metadata" +) + +type program struct { + as400 *collectors.AS400Collector +} + +func (p *program) Start(_ service.Service) error { + go p.run() + return nil +} + +func (p *program) Stop(_ service.Service) error { + if p.as400 != nil { + utils.Logger.Info("Stopping AS400 Collector...") + return p.as400.Stop() + } + return nil +} + +func (p *program) run() { + utils.InitLogger(config.ServiceLogFile) + cnf, err := config.GetCurrentConfig() + if err != nil { + utils.Logger.Fatal("error getting config: %v", err) + } + + db := database.GetDB() + err = db.Migrate(models.Log{}) + if err != nil { + utils.Logger.ErrorF("error migrating logs table: %v", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ctx = metadata.AppendToOutgoingContext(ctx, "key", cnf.CollectorKey) + ctx = metadata.AppendToOutgoingContext(ctx, "id", strconv.Itoa(int(cnf.CollectorID))) + ctx = metadata.AppendToOutgoingContext(ctx, "type", "collector") + + go pb.StartPing(cnf, ctx) + + logProcessor := logservice.GetLogProcessor() + go logProcessor.ProcessLogs(cnf, ctx) + + // Start AS400 Collector with configuration stream + p.as400 = collectors.NewAS400Collector() + if err := p.as400.Start(ctx, cnf); err != nil { + utils.Logger.Fatal("error starting AS400 collector: %v", err) + } + + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) + <-signals +} diff --git a/as400/serv/uninstall.go b/as400/serv/uninstall.go new file mode 100644 index 000000000..f7b669dd8 --- /dev/null +++ b/as400/serv/uninstall.go @@ -0,0 +1,16 @@ +package serv + +import ( + "github.com/utmstack/UTMStack/as400/utils" +) + +func UninstallService() { + err := utils.StopService("UTMStackAS400Collector") + if err != nil { + utils.Logger.Fatal("error stopping UTMStackAS400Collector: %v", err) + } + err = utils.UninstallService("UTMStackAS400Collector") + if err != nil { + utils.Logger.Fatal("error uninstalling UTMStackAS400Collector: %v", err) + } +} diff --git a/as400/updater/config/config.go b/as400/updater/config/config.go new file mode 100644 index 000000000..8daefeb23 --- /dev/null +++ b/as400/updater/config/config.go @@ -0,0 +1,54 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + "sync" + + "gopkg.in/yaml.v3" +) + +type Config struct { + Server string `json:"server" yaml:"server"` + SkipCertValidation bool `json:"insecure" yaml:"insecure"` +} + +var ( + cnf = Config{} + confOnce sync.Once +) + +func GetCurrentConfig() (*Config, error) { + var errR error + confOnce.Do(func() { + ex, err := os.Executable() + if err != nil { + errR = fmt.Errorf("error getting executable path: %v", err) + return + } + exPath := filepath.Dir(ex) + + configPath := filepath.Join(exPath, "config.yml") + content, err := os.ReadFile(configPath) + if err != nil { + errR = fmt.Errorf("error reading config file: %v", err) + return + } + + var loadedConfig Config + err = yaml.Unmarshal(content, &loadedConfig) + if err != nil { + errR = fmt.Errorf("error parsing config file: %v", err) + return + } + + cnf.Server = loadedConfig.Server + cnf.SkipCertValidation = loadedConfig.SkipCertValidation + }) + + if errR != nil { + return nil, errR + } + return &cnf, nil +} diff --git a/as400/updater/config/const.go b/as400/updater/config/const.go new file mode 100644 index 000000000..4be0bbacb --- /dev/null +++ b/as400/updater/config/const.go @@ -0,0 +1,23 @@ +package config + +import ( + "path/filepath" + + "github.com/utmstack/UTMStack/as400/updater/utils" +) + +const ( + SERV_LOG = "utmstack_as400_updater.log" + SERV_COLLECTOR_NAME = "UTMStackAS400Collector" + JAR_FILE = "as400-collector.jar" +) + +var ( + DependUrl = "https://%s:%s/private/dependencies/collector/as400/%s" + AgentManagerPort = "9000" + LogAuthProxyPort = "50051" + DependenciesPort = "9001" + + ServiceFile = "utmstack_as400_collector_service%s" + VersionPath = filepath.Join(utils.GetMyPath(), "version.json") +) diff --git a/as400/updater/go.mod b/as400/updater/go.mod new file mode 100644 index 000000000..6b8aba325 --- /dev/null +++ b/as400/updater/go.mod @@ -0,0 +1,49 @@ +module github.com/utmstack/UTMStack/as400/updater + +go 1.25.5 + +require ( + github.com/kardianos/service v1.2.4 + github.com/threatwinds/go-sdk v1.1.1 + github.com/threatwinds/logger v1.2.3 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.14.2 // indirect + github.com/bytedance/sonic/loader v0.4.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/gin-gonic/gin v1.11.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + golang.org/x/arch v0.23.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/as400/updater/go.sum b/as400/updater/go.sum new file mode 100644 index 000000000..b8177de20 --- /dev/null +++ b/as400/updater/go.sum @@ -0,0 +1,121 @@ +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= +github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= +github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= +github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +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/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/kardianos/service v1.2.4 h1:XNlGtZOYNx2u91urOdg/Kfmc+gfmuIo1Dd3rEi2OgBk= +github.com/kardianos/service v1.2.4/go.mod h1:E4V9ufUuY82F7Ztlu1eN9VXWIQxg8NoLQlmFe0MtrXc= +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.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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +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/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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +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/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.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +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/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/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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/threatwinds/go-sdk v1.1.1 h1:K1rtmjhYokGvj5z/o/wwn8gD6LvkgKfxv2MyMUezRUQ= +github.com/threatwinds/go-sdk v1.1.1/go.mod h1:N19iqJPaNAoWwZTCuFvV0hIvT0D1jOR1KkKYgAoPLmw= +github.com/threatwinds/logger v1.2.3 h1:V2SVAXzbq+/huCvIWOfqzMTH+WBHJxankyBgVG2hy1Y= +github.com/threatwinds/logger v1.2.3/go.mod h1:N+bJKvF4FQNJZLfQpVYWpr6D8iEAFnAQfHYqH5iR1TI= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +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/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= +golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +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/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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +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= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/as400/updater/main.go b/as400/updater/main.go new file mode 100644 index 000000000..2fe98c9ee --- /dev/null +++ b/as400/updater/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/utmstack/UTMStack/as400/updater/config" + "github.com/utmstack/UTMStack/as400/updater/service" + "github.com/utmstack/UTMStack/as400/updater/utils" +) + +func main() { + path := utils.GetMyPath() + utils.InitLogger(filepath.Join(path, "logs", config.SERV_LOG)) + + if len(os.Args) > 1 { + switch os.Args[1] { + case "install": + fmt.Println("Installing UTMStack AS400 Updater service...") + + fmt.Print(("Creating service ... ")) + service.InstallService() + fmt.Println("[OK]") + + fmt.Println("UTMStack AS400 Updater service installed correctly") + return + case "uninstall": + fmt.Println("Uninstalling UTMStack AS400 Updater service...") + service.UninstallService() + fmt.Println("Service uninstalled successfully") + return + case "start": + fmt.Println("Starting UTMStack AS400 Updater service...") + return + case "stop": + fmt.Println("Stopping UTMStack AS400 Updater service...") + // Will be handled by systemd + return + } + } + + service.RunService() +} diff --git a/as400/updater/models/version.go b/as400/updater/models/version.go new file mode 100644 index 000000000..ad40993be --- /dev/null +++ b/as400/updater/models/version.go @@ -0,0 +1,6 @@ +package models + +type Version struct { + Version string `json:"version"` + JarVersion string `json:"jar_version"` +} diff --git a/as400/updater/service/config.go b/as400/updater/service/config.go new file mode 100644 index 000000000..00f13bb8e --- /dev/null +++ b/as400/updater/service/config.go @@ -0,0 +1,15 @@ +package service + +import ( + "github.com/kardianos/service" +) + +func GetConfigServ() *service.Config { + svcConfig := &service.Config{ + Name: "UTMStackAS400Updater", + DisplayName: "UTMStack AS400 Updater", + Description: "UTMStack AS400 Collector Updater Service", + } + + return svcConfig +} diff --git a/as400/updater/service/install.go b/as400/updater/service/install.go new file mode 100644 index 000000000..bae17e789 --- /dev/null +++ b/as400/updater/service/install.go @@ -0,0 +1,52 @@ +package service + +import ( + "fmt" + "os" + + "github.com/kardianos/service" + "github.com/utmstack/UTMStack/as400/updater/utils" +) + +func InstallService() { + svcConfig := GetConfigServ() + prg := new(program) + newService, err := service.New(prg, svcConfig) + if err != nil { + fmt.Println("\nError creating new service: ", err) + os.Exit(1) + } + err = newService.Install() + if err != nil { + fmt.Println("\nError installing new service: ", err) + os.Exit(1) + } + + err = newService.Start() + if err != nil { + fmt.Println("\nError starting new service: ", err) + os.Exit(1) + } + utils.UpdaterLogger.Info("updater service installed successfully") +} + +func UninstallService() { + svcConfig := GetConfigServ() + prg := new(program) + newService, err := service.New(prg, svcConfig) + if err != nil { + fmt.Println("\nError creating new service: ", err) + os.Exit(1) + } + + err = newService.Stop() + if err != nil { + fmt.Println("\nWarning stopping service: ", err) + } + + err = newService.Uninstall() + if err != nil { + fmt.Println("\nError uninstalling service: ", err) + os.Exit(1) + } +} diff --git a/as400/updater/service/service.go b/as400/updater/service/service.go new file mode 100644 index 000000000..f598f5f9c --- /dev/null +++ b/as400/updater/service/service.go @@ -0,0 +1,43 @@ +package service + +import ( + "github.com/kardianos/service" + "github.com/utmstack/UTMStack/as400/updater/config" + "github.com/utmstack/UTMStack/as400/updater/updates" + "github.com/utmstack/UTMStack/as400/updater/utils" +) + +type program struct{} + +func (p *program) Start(s service.Service) error { + go p.run() + return nil +} + +func (p *program) Stop(s service.Service) error { + return nil +} + +func (p *program) run() { + cnf, err := config.GetCurrentConfig() + if err != nil { + utils.UpdaterLogger.ErrorF("error getting config: %v", err) + return + } + + updates.UpdateDependencies(cnf) +} + +func RunService() { + svcConfig := GetConfigServ() + prg := new(program) + newService, err := service.New(prg, svcConfig) + if err != nil { + utils.UpdaterLogger.Fatal("error creating service: %v", err) + } + + err = newService.Run() + if err != nil { + utils.UpdaterLogger.Fatal("error running service: %v", err) + } +} diff --git a/as400/updater/updates/update.go b/as400/updater/updates/update.go new file mode 100644 index 000000000..d8cac2d7c --- /dev/null +++ b/as400/updater/updates/update.go @@ -0,0 +1,243 @@ +package updates + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/utmstack/UTMStack/as400/updater/config" + "github.com/utmstack/UTMStack/as400/updater/models" + "github.com/utmstack/UTMStack/as400/updater/utils" +) + +const ( + checkEvery = 5 * time.Minute +) + +var currentVersion = models.Version{} + +func UpdateDependencies(cnf *config.Config) { + if utils.CheckIfPathExist(config.VersionPath) { + err := utils.ReadJson(config.VersionPath, ¤tVersion) + if err != nil { + utils.UpdaterLogger.ErrorF("error reading version file: %v", err) + } + } + + for { + time.Sleep(checkEvery) + + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, "version.json"), map[string]string{}, "version_new.json", utils.GetMyPath(), cnf.SkipCertValidation); err != nil { + utils.UpdaterLogger.ErrorF("error downloading version.json: %v", err) + continue + } + newVersion := models.Version{} + err := utils.ReadJson(filepath.Join(utils.GetMyPath(), "version_new.json"), &newVersion) + if err != nil { + utils.UpdaterLogger.ErrorF("error reading version file: %v", err) + continue + } + + binaryNeedsUpdate := newVersion.Version != currentVersion.Version + jarNeedsUpdate := newVersion.JarVersion != currentVersion.JarVersion + + if binaryNeedsUpdate || jarNeedsUpdate { + if binaryNeedsUpdate { + utils.UpdaterLogger.Info("New version of agent found: %s -> %s", currentVersion.Version, newVersion.Version) + } + if jarNeedsUpdate { + utils.UpdaterLogger.Info("New version of JAR found: %s -> %s", currentVersion.JarVersion, newVersion.JarVersion) + } + + if binaryNeedsUpdate { + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, fmt.Sprintf(config.ServiceFile, "")), map[string]string{}, fmt.Sprintf(config.ServiceFile, "_new"), utils.GetMyPath(), cnf.SkipCertValidation); err != nil { + utils.UpdaterLogger.ErrorF("error downloading agent: %v", err) + os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) + continue + } + + if err = utils.Execute("chmod", utils.GetMyPath(), "-R", "755", filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.ServiceFile, "_new"))); err != nil { + utils.UpdaterLogger.ErrorF("error executing chmod: %v", err) + } + } + + if jarNeedsUpdate { + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, config.JAR_FILE), map[string]string{}, config.JAR_FILE+"_new", utils.GetMyPath(), cnf.SkipCertValidation); err != nil { + utils.UpdaterLogger.ErrorF("error downloading JAR: %v", err) + if binaryNeedsUpdate { + os.Remove(filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.ServiceFile, "_new"))) + } + os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) + continue + } + } + + utils.UpdaterLogger.Info("Starting update process...") + err = runUpdateProcess(binaryNeedsUpdate, jarNeedsUpdate) + if err != nil { + utils.UpdaterLogger.ErrorF("error updating service: %v", err) + os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) + if binaryNeedsUpdate { + os.Remove(filepath.Join(utils.GetMyPath(), fmt.Sprintf(config.ServiceFile, "_new"))) + } + if jarNeedsUpdate { + os.Remove(filepath.Join(utils.GetMyPath(), config.JAR_FILE+"_new")) + } + } else { + utils.UpdaterLogger.Info("Update completed successfully") + if utils.CheckIfPathExist(config.VersionPath) { + err := utils.ReadJson(config.VersionPath, ¤tVersion) + if err != nil { + utils.UpdaterLogger.ErrorF("error reading updated version file: %v", err) + } + } + } + } else { + os.Remove(filepath.Join(utils.GetMyPath(), "version_new.json")) + } + } +} + +func runUpdateProcess(updateBinary, updateJar bool) error { + path := utils.GetMyPath() + + newBin := fmt.Sprintf(config.ServiceFile, "_new") + oldBin := fmt.Sprintf(config.ServiceFile, "") + backupBin := fmt.Sprintf(config.ServiceFile, ".old") + + if updateBinary { + agentNew := filepath.Join(path, newBin) + if _, err := os.Stat(agentNew); err != nil { + return fmt.Errorf("no _new binary found to update") + } + } + + if updateJar { + jarNew := filepath.Join(path, config.JAR_FILE+"_new") + if _, err := os.Stat(jarNew); err != nil { + return fmt.Errorf("no _new JAR found to update") + } + utils.UpdaterLogger.Info("New JAR file found, will be updated") + } + + if err := utils.StopService(config.SERV_COLLECTOR_NAME); err != nil { + return fmt.Errorf("error stopping agent: %v", err) + } + + time.Sleep(10 * time.Second) + + if updateBinary { + utils.UpdaterLogger.Info("Updating binary...") + backupPath := filepath.Join(path, backupBin) + if utils.CheckIfPathExist(backupPath) { + utils.UpdaterLogger.Info("Removing previous backup: %s", backupPath) + if err := os.Remove(backupPath); err != nil { + utils.UpdaterLogger.ErrorF("could not remove old backup: %v", err) + } + } + + if err := os.Rename(filepath.Join(path, oldBin), backupPath); err != nil { + return fmt.Errorf("error backing up old binary: %v", err) + } + + if err := os.Rename(filepath.Join(path, newBin), filepath.Join(path, oldBin)); err != nil { + os.Rename(backupPath, filepath.Join(path, oldBin)) + return fmt.Errorf("error renaming new binary: %v", err) + } + utils.UpdaterLogger.Info("Binary updated successfully") + } + + if updateJar { + utils.UpdaterLogger.Info("Updating JAR file...") + jarBackup := filepath.Join(path, config.JAR_FILE+".old") + jarCurrent := filepath.Join(path, config.JAR_FILE) + + if utils.CheckIfPathExist(jarBackup) { + if err := os.Remove(jarBackup); err != nil { + utils.UpdaterLogger.ErrorF("could not remove old JAR backup: %v", err) + } + } + + if utils.CheckIfPathExist(jarCurrent) { + if err := os.Rename(jarCurrent, jarBackup); err != nil { + utils.UpdaterLogger.ErrorF("error backing up JAR: %v", err) + } + } + + jarNew := filepath.Join(path, config.JAR_FILE+"_new") + if err := os.Rename(jarNew, jarCurrent); err != nil { + utils.UpdaterLogger.ErrorF("error installing new JAR: %v", err) + if utils.CheckIfPathExist(jarBackup) { + os.Rename(jarBackup, jarCurrent) + } + } else { + utils.UpdaterLogger.Info("JAR file updated successfully") + } + } + + if err := utils.StartService(config.SERV_COLLECTOR_NAME); err != nil { + rollbackAgent(oldBin, backupBin, path, updateBinary, updateJar) + return fmt.Errorf("error starting agent: %v", err) + } + + time.Sleep(30 * time.Second) + + isHealthy, err := utils.CheckIfServiceIsActive(config.SERV_COLLECTOR_NAME) + if err != nil || !isHealthy { + utils.UpdaterLogger.Info("New version failed health check, rolling back...") + rollbackAgent(oldBin, backupBin, path, updateBinary, updateJar) + return fmt.Errorf("rollback completed: new version failed health check") + } + + utils.UpdaterLogger.Info("Health check passed for agent") + + versionNewPath := filepath.Join(path, "version_new.json") + versionPath := filepath.Join(path, "version.json") + if utils.CheckIfPathExist(versionNewPath) { + if err := os.Rename(versionNewPath, versionPath); err != nil { + utils.UpdaterLogger.ErrorF("error updating version file: %v", err) + } else { + utils.UpdaterLogger.Info("Version file updated successfully") + } + } + + return nil +} + +func rollbackAgent(currentBin, backupBin, path string, binaryWasUpdated, jarWasUpdated bool) { + utils.UpdaterLogger.Info("Rolling back agent to previous version...") + + utils.StopService(config.SERV_COLLECTOR_NAME) + time.Sleep(5 * time.Second) + + if binaryWasUpdated { + utils.UpdaterLogger.Info("Rolling back binary...") + os.Remove(filepath.Join(path, currentBin)) + os.Rename(filepath.Join(path, backupBin), filepath.Join(path, currentBin)) + utils.UpdaterLogger.Info("Binary rolled back successfully") + } + + if jarWasUpdated { + utils.UpdaterLogger.Info("Rolling back JAR file...") + jarCurrent := filepath.Join(path, config.JAR_FILE) + jarBackup := filepath.Join(path, config.JAR_FILE+".old") + + if utils.CheckIfPathExist(jarBackup) { + os.Remove(jarCurrent) + os.Rename(jarBackup, jarCurrent) + utils.UpdaterLogger.Info("JAR file rolled back successfully") + } + } + + utils.StartService(config.SERV_COLLECTOR_NAME) + os.Remove(filepath.Join(path, "version_new.json")) + if jarWasUpdated { + os.Remove(filepath.Join(path, config.JAR_FILE+"_new")) + } + if binaryWasUpdated { + os.Remove(filepath.Join(path, fmt.Sprintf(config.ServiceFile, "_new"))) + } + + utils.UpdaterLogger.Info("Rollback completed for agent") +} diff --git a/as400/updater/utils/cmd.go b/as400/updater/utils/cmd.go new file mode 100644 index 000000000..eae4140d9 --- /dev/null +++ b/as400/updater/utils/cmd.go @@ -0,0 +1,39 @@ +package utils + +import ( + "errors" + "os/exec" + + twsdk "github.com/threatwinds/go-sdk/entities" +) + +func ExecuteWithResult(c string, dir string, arg ...string) (string, bool) { + cmd := exec.Command(c, arg...) + + cmd.Dir = dir + if errors.Is(cmd.Err, exec.ErrDot) { + cmd.Err = nil + } + + out, err := cmd.Output() + if err != nil { + return string(out[:]) + err.Error(), true + } + + if string(out[:]) == "" { + return "Command executed successfully but no output", false + } + validUtf8Out, _, err := twsdk.ValidateString(string(out[:]), false) + if err != nil { + return string(out[:]) + err.Error(), true + } + + return validUtf8Out, false +} + +func Execute(c string, dir string, arg ...string) error { + cmd := exec.Command(c, arg...) + cmd.Dir = dir + + return cmd.Run() +} diff --git a/as400/updater/utils/download.go b/as400/updater/utils/download.go new file mode 100644 index 000000000..055c44881 --- /dev/null +++ b/as400/updater/utils/download.go @@ -0,0 +1,48 @@ +package utils + +import ( + "crypto/tls" + "fmt" + "io" + "net/http" + "os" + "path/filepath" +) + +func DownloadFile(url string, headers map[string]string, fileName string, path string, skipTlsVerification bool) error { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return fmt.Errorf("error creating new request: %v", err) + } + for key, value := range headers { + req.Header.Add(key, value) + } + + client := &http.Client{} + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTlsVerification}, + } + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("error sending request: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("expected status %d; got %d", http.StatusOK, resp.StatusCode) + } + + out, err := os.Create(filepath.Join(path, fileName)) + if err != nil { + return fmt.Errorf("error creating file: %v", err) + } + defer func() { _ = out.Close() }() + + _, err = io.Copy(out, resp.Body) + if err != nil { + return fmt.Errorf("error copying file: %v", err) + } + + return nil +} diff --git a/as400/updater/utils/files.go b/as400/updater/utils/files.go new file mode 100644 index 000000000..6b0180721 --- /dev/null +++ b/as400/updater/utils/files.go @@ -0,0 +1,92 @@ +package utils + +import ( + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" +) + +func GetMyPath() string { + ex, err := os.Executable() + if err != nil { + return "" + } + exPath := filepath.Dir(ex) + return exPath +} + +func CreatePathIfNotExist(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + if err := os.MkdirAll(path, 0755); err != nil { + return fmt.Errorf("error creating path: %v", err) + } + } else if err != nil { + return fmt.Errorf("error checking path: %v", err) + } + return nil +} + +func CheckIfPathExist(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + return true +} + +func ReadJson(fileName string, data interface{}) error { + content, err := os.ReadFile(fileName) + if err != nil { + return err + } + + err = json.Unmarshal(content, data) + if err != nil { + return err + } + + return nil +} + +func WriteStringToFile(fileName string, body string) error { + file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.ModePerm) + if err != nil { + return err + } + defer func() { _ = file.Close() }() + + _, err = file.WriteString(body) + return err +} + +func WriteJSON(path string, data interface{}) error { + jsonData, err := json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + + err = WriteStringToFile(path, string(jsonData)) + if err != nil { + return err + } + + return nil +} + +func copyFile(src, dst string) error { + sourceFile, err := os.Open(src) + if err != nil { + return err + } + defer sourceFile.Close() + + destFile, err := os.Create(dst) + if err != nil { + return err + } + defer destFile.Close() + + _, err = io.Copy(destFile, sourceFile) + return err +} diff --git a/as400/updater/utils/logger.go b/as400/updater/utils/logger.go new file mode 100644 index 000000000..fc0cbb757 --- /dev/null +++ b/as400/updater/utils/logger.go @@ -0,0 +1,20 @@ +package utils + +import ( + "sync" + + "github.com/threatwinds/logger" +) + +var ( + UpdaterLogger *logger.Logger + loggerOnceInstance sync.Once +) + +func InitLogger(filename string) { + loggerOnceInstance.Do(func() { + UpdaterLogger = logger.NewLogger( + &logger.Config{Format: "text", Level: 100, Output: filename, Retries: 3, Wait: 5}, + ) + }) +} diff --git a/as400/updater/utils/services.go b/as400/updater/utils/services.go new file mode 100644 index 000000000..26b73bd18 --- /dev/null +++ b/as400/updater/utils/services.go @@ -0,0 +1,55 @@ +package utils + +import ( + "fmt" + "strings" +) + +func CheckIfServiceIsActive(serv string) (bool, error) { + path := GetMyPath() + output, errB := ExecuteWithResult("systemctl", path, "is-active", serv) + if errB { + return false, nil + } + serviceStatus := strings.ToLower(strings.TrimSpace(output)) + return serviceStatus == "active", nil +} + +func RestartService(serv string) error { + path := GetMyPath() + isRunning, err := CheckIfServiceIsActive(serv) + if err != nil { + return fmt.Errorf("error checking if %s service is active: %v", serv, err) + } + + if isRunning { + err := Execute("systemctl", path, "restart", serv) + if err != nil { + return fmt.Errorf("error restarting service: %v", err) + } + } else { + err := Execute("systemctl", path, "start", serv) + if err != nil { + return fmt.Errorf("error starting service: %v", err) + } + } + return nil +} + +func StopService(name string) error { + path := GetMyPath() + err := Execute("systemctl", path, "stop", name) + if err != nil { + return fmt.Errorf("error stopping service: %v", err) + } + return nil +} + +func StartService(name string) error { + path := GetMyPath() + err := Execute("systemctl", path, "start", name) + if err != nil { + return fmt.Errorf("error starting service: %v", err) + } + return nil +} diff --git a/as400/updater/utils/zip.go b/as400/updater/utils/zip.go new file mode 100644 index 000000000..ecb690a38 --- /dev/null +++ b/as400/updater/utils/zip.go @@ -0,0 +1,52 @@ +package utils + +import ( + "archive/zip" + "io" + "os" + "path" + "path/filepath" +) + +func Unzip(zipFile, destPath string) error { + archive, err := zip.OpenReader(zipFile) + if err != nil { + return err + } + defer archive.Close() + + for _, f := range archive.File { + err := func() error { + filePath := path.Join(destPath, f.Name) + if f.FileInfo().IsDir() { + os.MkdirAll(filePath, os.ModePerm) + return nil + } + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + return err + } + + dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer dstFile.Close() + + fileInArchive, err := f.Open() + if err != nil { + return err + } + defer fileInArchive.Close() + + if _, err := io.Copy(dstFile, fileInArchive); err != nil { + return err + } + + return nil + }() + if err != nil { + return err + } + } + return nil +} diff --git a/as400/updates/dependencies.go b/as400/updates/dependencies.go new file mode 100644 index 000000000..f2db5937c --- /dev/null +++ b/as400/updates/dependencies.go @@ -0,0 +1,34 @@ +package updates + +import ( + "fmt" + + "github.com/utmstack/UTMStack/as400/config" + "github.com/utmstack/UTMStack/as400/utils" +) + +func DownloadVersion(address string, insecure bool) error { + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, address, config.DependenciesPort, "version.json"), map[string]string{}, "version.json", utils.GetMyPath(), insecure); err != nil { + return fmt.Errorf("error downloading version.json : %v", err) + } + + return nil + +} + +func DownloadUpdater(address string, insecure bool) error { + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, address, config.DependenciesPort, "utmstack_updater_service"), map[string]string{}, "utmstack_updater_service", utils.GetMyPath(), insecure); err != nil { + return fmt.Errorf("error downloading utmstack_updater_service : %v", err) + } + + return nil +} + +func DownloadJar(address string, insecure bool) error { + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, address, config.DependenciesPort, "as400-collector.jar"), map[string]string{}, "as400-collector.jar", utils.GetMyPath(), insecure); err != nil { + return fmt.Errorf("error downloading as400-collector.jar : %v", err) + } + + return nil + +} diff --git a/as400/utils/address.go b/as400/utils/address.go new file mode 100644 index 000000000..8a6a4945d --- /dev/null +++ b/as400/utils/address.go @@ -0,0 +1,24 @@ +package utils + +import ( + "errors" + "net" +) + +func GetIPAddress() (string, error) { + ipAddress, err := net.InterfaceAddrs() + if err != nil { + return "", err + } + + for _, addr := range ipAddress { + ipNet, ok := addr.(*net.IPNet) + if ok && !ipNet.IP.IsLoopback() { + if ipNet.IP.To4() != nil { + return ipNet.IP.String(), nil + } + } + } + + return "", errors.New("failed to get IP address") +} diff --git a/as400/utils/banner.go b/as400/utils/banner.go new file mode 100644 index 000000000..cb7c45f59 --- /dev/null +++ b/as400/utils/banner.go @@ -0,0 +1,17 @@ +package utils + +import "fmt" + +func PrintBanner() { + banner := "\n" + + "..........................................................................\n" + + " _ _ _ _____ _ _ \n" + + " | | | | | | / ____| | | | | \n" + + " | | | | | |_ _ __ ___ | (___ | |_ __ _ ___ | | __ \n" + + " | | | | | __| | '_ ` _ \\ \\___ \\ | __| / _` | / __| | |/ / \n" + + " | |__| | | |_ | | | | | | ____) | | |_ | (_| | | (__ | < \n" + + " \\____/ \\__| |_| |_| |_| |_____/ \\__| \\__,_| \\___| |_|\\_\\ \n" + + ".........................................................................." + + fmt.Println(banner) +} diff --git a/as400/utils/cmd.go b/as400/utils/cmd.go new file mode 100644 index 000000000..334f5335f --- /dev/null +++ b/as400/utils/cmd.go @@ -0,0 +1,12 @@ +package utils + +import ( + "os/exec" +) + +func Execute(c string, dir string, arg ...string) error { + cmd := exec.Command(c, arg...) + cmd.Dir = dir + + return cmd.Run() +} diff --git a/as400/utils/crypt.go b/as400/utils/crypt.go new file mode 100644 index 000000000..48fa35deb --- /dev/null +++ b/as400/utils/crypt.go @@ -0,0 +1,22 @@ +package utils + +import ( + "encoding/base64" +) + +func GenerateKey(baseKey string) ([]byte, error) { + info, err := GetOsInfo() + if err != nil { + return nil, Logger.ErrorF("error getting os info: %v", err) + } + + data := []byte(info.Hostname + info.Mac + info.OsType) + base64Key := base64.StdEncoding.EncodeToString(data) + return []byte(baseKey + base64Key), nil +} + +func GenerateKeyByUUID(baseKey string, uuid string) ([]byte, error) { + data := []byte(baseKey + uuid) + base64Key := base64.StdEncoding.EncodeToString(data) + return []byte(base64Key), nil +} diff --git a/as400/utils/delay.go b/as400/utils/delay.go new file mode 100644 index 000000000..e3000ea71 --- /dev/null +++ b/as400/utils/delay.go @@ -0,0 +1,11 @@ +package utils + +import "time" + +func IncrementReconnectDelay(delay time.Duration, maxReconnectDelay time.Duration) time.Duration { + delay *= 2 + if delay > maxReconnectDelay { + delay = maxReconnectDelay + } + return delay +} diff --git a/as400/utils/download.go b/as400/utils/download.go new file mode 100644 index 000000000..c57753cbf --- /dev/null +++ b/as400/utils/download.go @@ -0,0 +1,73 @@ +package utils + +import ( + "crypto/tls" + "fmt" + "io" + "net/http" + "os" + "path/filepath" +) + +func DownloadFile(url string, headers map[string]string, fileName string, path string, skipTlsVerification bool) error { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return fmt.Errorf("error creating new request: %v", err) + } + for key, value := range headers { + req.Header.Add(key, value) + } + + client := &http.Client{} + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTlsVerification}, + } + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("error sending request: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("expected status %d; got %d", http.StatusOK, resp.StatusCode) + } + + out, err := os.Create(filepath.Join(path, fileName)) + if err != nil { + return fmt.Errorf("error creating file: %v", err) + } + defer func() { _ = out.Close() }() + + _, err = io.Copy(out, resp.Body) + if err != nil { + return fmt.Errorf("error copying file: %v", err) + } + + return nil +} + +func InstallJava() error { + err := Execute("apt-get", "", "update") + if err != nil { + return err + } + err = Execute("apt", "", "install", "-y", "openjdk-17-jdk") + if err != nil { + return err + } + return nil + +} + +func UninstallJava() error { + err := Execute("apt", "", "remove", "-y", "openjdk-17-jdk") + if err != nil { + return err + } + err = Execute("apt", "", "autoremove", "-y") + if err != nil { + return err + } + return nil +} diff --git a/as400/utils/files.go b/as400/utils/files.go new file mode 100644 index 000000000..24b45e352 --- /dev/null +++ b/as400/utils/files.go @@ -0,0 +1,110 @@ +package utils + +import ( + "encoding/json" + "os" + "path/filepath" + + "gopkg.in/yaml.v2" +) + +func GetMyPath() string { + ex, err := os.Executable() + if err != nil { + return "" + } + exPath := filepath.Dir(ex) + return exPath +} + +func ReadYAML(path string, result interface{}) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer func() { _ = file.Close() }() + + d := yaml.NewDecoder(file) + if err := d.Decode(result); err != nil { + return err + } + + return nil +} + +func WriteStringToFile(fileName string, body string) error { + // Create directory if it doesn't exist + dir := filepath.Dir(fileName) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.ModePerm) + if err != nil { + return err + } + defer func() { _ = file.Close() }() + + _, err = file.WriteString(body) + return err +} + +func WriteYAML(url string, data interface{}) error { + config, err := yaml.Marshal(data) + if err != nil { + return err + } + + err = WriteStringToFile(url, string(config)) + if err != nil { + return err + } + + return nil +} + +func WriteJSON(path string, data interface{}) error { + jsonData, err := json.MarshalIndent(data, "", " ") + if err != nil { + return err + } + + err = WriteStringToFile(path, string(jsonData)) + if err != nil { + return err + } + + return nil +} + +func ReadJson(fileName string, data interface{}) error { + content, err := os.ReadFile(fileName) + if err != nil { + return err + } + + err = json.Unmarshal(content, data) + if err != nil { + return err + } + + return nil +} + +func CreatePathIfNotExist(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + if err := os.MkdirAll(path, 0755); err != nil { + return Logger.ErrorF("error creating path: %v", err) + } + } else if err != nil { + return Logger.ErrorF("error checking path: %v", err) + } + return nil +} + +func CheckIfPathExist(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + return true +} diff --git a/as400/utils/host.go b/as400/utils/host.go new file mode 100644 index 000000000..e79c972c3 --- /dev/null +++ b/as400/utils/host.go @@ -0,0 +1,21 @@ +package utils + +import "net" + +func GetHostAliases(hostname string) ([]string, error) { + var aliases []string + addresses, err := net.LookupHost(hostname) + if err != nil { + return nil, err + } + + for _, address := range addresses { + newAliases, err := net.LookupAddr(address) + if err != nil { + return nil, err + } + aliases = append(aliases, newAliases...) + } + + return aliases, nil +} diff --git a/as400/utils/logger.go b/as400/utils/logger.go new file mode 100644 index 000000000..a93ac7455 --- /dev/null +++ b/as400/utils/logger.go @@ -0,0 +1,44 @@ +package utils + +import ( + "path/filepath" + "sync" + + "github.com/threatwinds/logger" +) + +var ( + Logger *logger.Logger + loggerOnceInstance sync.Once + logLevelConfigFile = filepath.Join(GetMyPath(), "log_level.yml") + LogLevelMap = map[string]int{ + "debug": 100, + "info": 200, + "notice": 300, + "warning": 400, + "error": 500, + "critical": 502, + "alert": 509, + } +) + +type LogLevels struct { + Level string `yaml:"level"` +} + +func InitLogger(filename string) { + logLevel := LogLevels{} + err := ReadYAML(logLevelConfigFile, &logLevel) + if err != nil { + logLevel.Level = "info" + } + logLevelInt := 200 + if val, ok := LogLevelMap[logLevel.Level]; ok { + logLevelInt = val + } + loggerOnceInstance.Do(func() { + Logger = logger.NewLogger( + &logger.Config{Format: "text", Level: logLevelInt, Output: filename, Retries: 3, Wait: 5}, + ) + }) +} diff --git a/as400/utils/os.go b/as400/utils/os.go new file mode 100644 index 000000000..1b095729f --- /dev/null +++ b/as400/utils/os.go @@ -0,0 +1,62 @@ +package utils + +import ( + "os" + "os/user" + "strconv" + "strings" + + "github.com/elastic/go-sysinfo" +) + +type OSInfo struct { + Hostname string + OsType string + Platform string + CurrentUser string + Mac string + OsMajorVersion string + OsMinorVersion string + Aliases string + Addresses string +} + +func GetOsInfo() (OSInfo, error) { + var info OSInfo + + hostInfo, err := sysinfo.Host() + if err != nil { + return info, Logger.ErrorF("error getting host info: %v", err) + } + info.OsType = hostInfo.Info().OS.Type + info.Platform = hostInfo.Info().OS.Platform + info.Mac = strings.Join(hostInfo.Info().MACs, ",") + info.OsMajorVersion = strconv.Itoa(hostInfo.Info().OS.Major) + info.OsMinorVersion = strconv.Itoa(hostInfo.Info().OS.Minor) + info.Addresses = strings.Join(hostInfo.Info().IPs, ",") + + hostName, err := os.Hostname() + if err != nil { + return info, Logger.ErrorF("error getting hostname: %v", err) + } + info.Hostname = hostName + + currentUser, err := user.Current() + if err != nil { + return info, Logger.ErrorF("error getting user: %v", err) + } + info.CurrentUser = currentUser.Username + + aliases, err := GetHostAliases(hostInfo.Info().Hostname) + if err != nil { + aliases = aliases[:0] + aliases = append(aliases, "") + } + if len(aliases) == 1 && strings.Contains(aliases[0], "any") { + aliases = aliases[:0] + aliases = append(aliases, "") + } + info.Aliases = strings.Join(aliases, ",") + + return info, nil +} diff --git a/as400/utils/port.go b/as400/utils/port.go new file mode 100644 index 000000000..af1e06596 --- /dev/null +++ b/as400/utils/port.go @@ -0,0 +1,29 @@ +package utils + +import ( + "fmt" + "net" + "time" +) + +func ArePortsReachable(ip string, ports ...string) error { + var conn net.Conn + var err error + +external: + for _, port := range ports { + for i := 0; i < 3; i++ { + conn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%s", ip, port), 5*time.Second) + if err == nil { + conn.Close() + continue external + } + time.Sleep(5 * time.Second) + } + if err != nil { + return Logger.ErrorF("cannot connect to %s on port %s: %v", ip, port, err) + } + } + + return nil +} diff --git a/as400/utils/services.go b/as400/utils/services.go new file mode 100644 index 000000000..9e9a19808 --- /dev/null +++ b/as400/utils/services.go @@ -0,0 +1,71 @@ +package utils + +import ( + "fmt" + "os" +) + +func StopService(name string) error { + path := GetMyPath() + err := Execute("systemctl", path, "stop", name) + if err != nil { + return Logger.ErrorF("error stopping service: %v", err) + } + return nil +} + +func UninstallService(name string) error { + path := GetMyPath() + err := Execute("systemctl", path, "disable", name) + if err != nil { + return Logger.ErrorF("error uninstalling service: %v", err) + } + err = Execute("rm", "/etc/systemd/system/", "/etc/systemd/system/"+name+".service") + if err != nil { + return Logger.ErrorF("error uninstalling service: %v", err) + } + return nil +} + +func CheckIfServiceIsInstalled(serv string) (bool, error) { + path := GetMyPath() + err := Execute("systemctl", path, "status", serv) + return err == nil, nil +} + +func CreateLinuxService(serviceName string, execStart string) error { + servicePath := "/etc/systemd/system/" + serviceName + ".service" + if !CheckIfPathExist(servicePath) { + file, err := os.Create(servicePath) + if err != nil { + return Logger.ErrorF("error creating %s file: %v", servicePath, err) + } + defer func() { _ = file.Close() }() + + serviceContent := fmt.Sprintf(`[Unit] +Description=%s +After=network.target + +[Service] +ExecStart=%s +Restart=always + +[Install] +WantedBy=multi-user.target +`, serviceName, execStart) + + _, err = file.WriteString(serviceContent) + if err != nil { + return err + } + + err = file.Sync() + if err != nil { + return err + } + } else { + return Logger.ErrorF("service %s already exists", serviceName) + } + + return nil +} diff --git a/as400/utils/updater.go b/as400/utils/updater.go new file mode 100644 index 000000000..0083171ee --- /dev/null +++ b/as400/utils/updater.go @@ -0,0 +1,31 @@ +package utils + +import "fmt" + +func InstallUpdater() error { + updaterPath := GetMyPath() + "/utmstack_updater_service" + + if err := Execute("chmod", GetMyPath(), "+x", updaterPath); err != nil { + return fmt.Errorf("error setting execute permissions: %v", err) + } + + if err := Execute(updaterPath, GetMyPath(), "install"); err != nil { + return fmt.Errorf("error installing updater service: %v", err) + } + + return nil +} + +func UninstallUpdater() error { + updaterPath := GetMyPath() + "/utmstack_updater_service" + + if !CheckIfPathExist(updaterPath) { + return nil + } + + if err := Execute(updaterPath, GetMyPath(), "uninstall"); err != nil { + return fmt.Errorf("error uninstalling updater service: %v", err) + } + + return nil +} diff --git a/as400/version.json b/as400/version.json new file mode 100644 index 000000000..ef90da4ab --- /dev/null +++ b/as400/version.json @@ -0,0 +1,4 @@ +{ + "version": "1.0.0", + "jar_version": "1.0.0" +} From 52508cb2600020c06441359afc2d5d5749c7f65e Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Sat, 14 Feb 2026 00:14:32 -0500 Subject: [PATCH 076/240] feat(pipeline): update UTMStack collector build process to include AS400 collector --- .github/workflows/v11-deployment-pipeline.yml | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/workflows/v11-deployment-pipeline.yml b/.github/workflows/v11-deployment-pipeline.yml index 65d09e245..5e176e0dc 100644 --- a/.github/workflows/v11-deployment-pipeline.yml +++ b/.github/workflows/v11-deployment-pipeline.yml @@ -187,7 +187,7 @@ jobs: retention-days: 1 build_utmstack_collector: - name: Build UTMStack Collector + name: Build UTMStack Collectors needs: [setup_deployment] if: ${{ needs.setup_deployment.outputs.tag != '' }} runs-on: ubuntu-24.04 @@ -202,11 +202,24 @@ jobs: GOOS=linux GOARCH=amd64 go build -o utmstack_collector -v -ldflags "-X 'github.com/utmstack/UTMStack/utmstack-collector/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . + - name: Build UTMStack AS400 Collector + run: | + echo "Building UTMStack AS400 Collector..." + cd ${{ github.workspace }}/as400 + + GOOS=linux GOARCH=amd64 go build -o utmstack_as400_collector_service -v -ldflags "-X 'github.com/utmstack/UTMStack/as400/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . + + cd ${{ github.workspace }}/as400/updater + go build -o utmstack_as400_updater_service . + - name: Upload collector binary as artifact uses: actions/upload-artifact@v4 with: name: utmstack-collector - path: ${{ github.workspace }}/utmstack-collector/utmstack_collector + path: | + ${{ github.workspace }}/utmstack-collector/utmstack_collector + ${{ github.workspace }}/as400/utmstack_as400_collector_service + ${{ github.workspace }}/as400/updater/utmstack_as400_updater_service retention-days: 1 build_agent_manager: @@ -236,12 +249,16 @@ jobs: GOOS=linux GOARCH=amd64 go build -o agent-manager -v . mkdir -p ./dependencies/collector - curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/collector/linux-as400-collector.zip" -o ./dependencies/collector/linux-as400-collector.zip - curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/collector/windows-as400-collector.zip" -o ./dependencies/collector/windows-as400-collector.zip - cp "${{ github.workspace }}/utmstack-collector/utmstack_collector" ./dependencies/collector/ cp "${{ github.workspace }}/utmstack-collector/version.json" ./dependencies/collector/ + mkdir -p ./dependencies/collector/as400 + curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/collector/as400-collector.jar" -o ./dependencies/collector/as400/as400-collector.jar + + cp "${{ github.workspace }}/utmstack-collector/as400/utmstack_as400_collector_service" ./dependencies/collector/as400/ + cp "${{ github.workspace }}/utmstack-collector/as400/updater/utmstack_as400_updater_service" ./dependencies/collector/as400/ + cp "${{ github.workspace }}/as400/version.json" ./dependencies/collector/as400/ + mkdir -p ./dependencies/agent/ curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/agent/utmstack_agent_dependencies_linux.zip" -o ./dependencies/agent/utmstack_agent_dependencies_linux.zip curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/agent/utmstack_agent_dependencies_windows.zip" -o ./dependencies/agent/utmstack_agent_dependencies_windows.zip From 949d1c8553860b97f7dec1148bdb0c011f8a895e Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Sat, 14 Feb 2026 00:28:51 -0500 Subject: [PATCH 077/240] feat(ibm-as400): update filter to version 3.0.1 with enhanced JSON parsing. --- filters/ibm/ibm_as_400.yml | 54 +++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/filters/ibm/ibm_as_400.yml b/filters/ibm/ibm_as_400.yml index 0c94221aa..40ae2d6d3 100644 --- a/filters/ibm/ibm_as_400.yml +++ b/filters/ibm/ibm_as_400.yml @@ -1,12 +1,58 @@ -# IBM AS 400 filter version 3.0.0 +# IBM AS 400 filter version 3.0.1 # Support Java Collector Syslog messsages pipeline: - dataTypes: - ibm-as400 steps: + - json: + source: raw + where: contains("raw", "\"msg\":") && contains("raw", "\"args\":") + - grok: + source: raw patterns: - - field_name: log.message - pattern: '(.*)' - source: raw \ No newline at end of file + - fieldName: log.message + pattern: '{{.greedy}}' + where: '!contains("raw", "\"msg\":") || !contains("raw", "\"args\":")' + + - rename: + from: + - log.hostname + to: origin.host + + - rename: + from: + - log.sourceIp + to: origin.ip + + - rename: + from: + - log.destinationIp + to: target.ip + + - rename: + from: + - log.file + to: origin.file + + - rename: + from: + - log.severityText + to: severity + + # Adding geolocation to origin.ip + - dynamic: + plugin: com.utmstack.geolocation + params: + source: origin.ip + destination: origin.geolocation + where: exists("origin.ip") + + # Adding geolocation to target.ip + - dynamic: + plugin: com.utmstack.geolocation + params: + source: target.ip + destination: target.geolocation + where: exists("target.ip") \ No newline at end of file From 9cb31c57f43cd7b3779fd8e5a0df6887daa83588 Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Mon, 16 Feb 2026 19:32:21 +0300 Subject: [PATCH 078/240] update vmware-esxi filter --- filters/vmware/vmware-esxi.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/filters/vmware/vmware-esxi.yml b/filters/vmware/vmware-esxi.yml index 7c533dd72..6b6f5b6b0 100644 --- a/filters/vmware/vmware-esxi.yml +++ b/filters/vmware/vmware-esxi.yml @@ -75,6 +75,14 @@ pipeline: - fieldName: log.subModuleIdentifier pattern: '{{.word}}\]' source: log.originIdComponent + + - grok: + patterns: + - fieldName: log.irrelevant2 + pattern: '{{.data}}level{{.space}}=' + - fieldName: log.level + pattern: '{{.integer}}' + source: log.message # Removing unused caracters - trim: From f5efbf9b9139b9c5e960424ff4e9807f00c35e3f Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Mon, 16 Feb 2026 19:45:41 +0300 Subject: [PATCH 079/240] update version vmware-esxi filter --- filters/vmware/vmware-esxi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filters/vmware/vmware-esxi.yml b/filters/vmware/vmware-esxi.yml index 6b6f5b6b0..a415d2e1f 100644 --- a/filters/vmware/vmware-esxi.yml +++ b/filters/vmware/vmware-esxi.yml @@ -1,4 +1,4 @@ -# VMWare-ESXi, version 3.0.1 +# VMWare-ESXi, version 3.0.2 # # Based on docs and real logs provided # Support VMWare-ESXi log From 4695f061ad367d97e01a20b09584683e14e2d9c0 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 16 Feb 2026 11:44:10 -0600 Subject: [PATCH 080/240] feat(collector): enhance collector configuration management and validation --- .../validators/UniqueServerNameValidator.java | 2 +- ...UtmModuleGroupConfigurationRepository.java | 2 +- .../UtmModuleGroupConfigurationService.java | 4 +- .../UtmModuleGroupService.java | 38 +++++++++++++++++++ .../service/collectors/CollectorService.java | 2 +- .../collectors/dto/CollectorConfigDTO.java | 1 - 6 files changed, 42 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java index 437fcbcd1..58f5a5159 100644 --- a/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java +++ b/backend/src/main/java/com/park/utmstack/domain/collector/validators/UniqueServerNameValidator.java @@ -12,7 +12,7 @@ public class UniqueServerNameValidator implements ConstraintValidator keys, ConstraintValidatorContext context) { - if (keys == null || keys.isEmpty()) return false; + if (keys == null ) return false; long duplicates = keys.stream() .filter(k -> "Hostname".equals(k.getConfName())) diff --git a/backend/src/main/java/com/park/utmstack/repository/UtmModuleGroupConfigurationRepository.java b/backend/src/main/java/com/park/utmstack/repository/UtmModuleGroupConfigurationRepository.java index bcf25f983..3c0e59ac9 100644 --- a/backend/src/main/java/com/park/utmstack/repository/UtmModuleGroupConfigurationRepository.java +++ b/backend/src/main/java/com/park/utmstack/repository/UtmModuleGroupConfigurationRepository.java @@ -10,7 +10,7 @@ /** * Spring Data repository for the UtmModuleGroupConfiguration entity. */ -@SuppressWarnings("unused") + @Repository public interface UtmModuleGroupConfigurationRepository extends JpaRepository { diff --git a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java index 59bde8c84..e2b649c2c 100644 --- a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java +++ b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupConfigurationService.java @@ -5,6 +5,7 @@ import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; import com.park.utmstack.domain.application_modules.enums.ModuleName; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; +import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.event_processor.EventProcessorManagerService; import com.park.utmstack.util.CipherUtil; @@ -36,8 +37,6 @@ public class UtmModuleGroupConfigurationService { private final UtmModuleGroupConfigurationRepository moduleConfigurationRepository; private final UtmModuleRepository moduleRepository; - private final EventProcessorManagerService eventProcessorManagerService; - public void createConfigurationKeys(List keys) throws Exception { final String ctx = CLASSNAME + ".createConfigurationKeys"; @@ -54,7 +53,6 @@ public void createConfigurationKeys(List keys) thro * Update configuration of the application modules * * @param keys List of configuration keys to save - * @throws Exception In case of any error */ public UtmModule updateConfigurationKeys(Long moduleId, List keys) { final String ctx = CLASSNAME + ".updateConfigurationKeys"; diff --git a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java index 0e71ac3b0..c9ff60392 100644 --- a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java +++ b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java @@ -13,9 +13,11 @@ import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.util.CipherUtil; +import com.park.utmstack.util.exceptions.ApiException; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -194,4 +196,40 @@ private void handleModuleDeactivationIfNeeded(UtmModuleGroup group, Long collect } } + public void updateCollectorConfigurationKeys(CollectorConfigDTO collectorConfig) { + final String ctx = CLASSNAME + ".updateCollectorConfigurationKeys"; + try { + + List dbConfigs = moduleGroupRepository + .findAllByModuleIdAndCollector(collectorConfig.getModuleId(), + String.valueOf(collectorConfig.getCollector().getId())); + + List keys = collectorConfig.getKeys(); + + if (collectorConfig.getKeys().isEmpty()) { + moduleGroupRepository.deleteAll(dbConfigs); + } else { + for (UtmModuleGroupConfiguration key : keys) { + if (key.getConfDataType().equals("password")){ + key.setConfValue(CipherUtil.encrypt(key.getConfValue(), System.getenv(Constants.ENV_ENCRYPTION_KEY))); + } + } + List keyGroupIds = keys.stream() + .map(UtmModuleGroupConfiguration::getGroupId) + .toList(); + + List groupsToDelete = dbConfigs.stream() + .filter(utmModuleGroup -> !keyGroupIds.contains(utmModuleGroup.getId())) + .collect(Collectors.toList()); + + moduleGroupRepository.deleteAll(groupsToDelete); + moduleGroupConfigurationRepository.saveAll(keys); + } + + } catch (Exception e) { + log.error("{}: Error updating collector configuration keys for collector id {}: {}", ctx, collectorConfig.getCollector().getId(), e.getMessage()); + throw new ApiException(String.format("%s: Error updating collector configuration keys for collector id %d", ctx, collectorConfig.getCollector().getId()), HttpStatus.INTERNAL_SERVER_ERROR); + } + } + } diff --git a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java index b5b7cd386..f65c720f3 100644 --- a/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java +++ b/backend/src/main/java/com/park/utmstack/service/collectors/CollectorService.java @@ -45,7 +45,7 @@ public class CollectorService { public void upsertCollectorConfig(CollectorConfigDTO collectorConfig) { - this.moduleGroupConfigurationService.updateConfigurationKeys(collectorConfig.getModuleId(), collectorConfig.getKeys()); + this.moduleGroupService.updateCollectorConfigurationKeys(collectorConfig); CollectorOuterClass.CollectorConfig collector = CollectorConfigBuilder.build(collectorConfig); collectorGrpcService.upsertCollectorConfig(collector); diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java index 562d1bad6..fe78c2f11 100644 --- a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java +++ b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java @@ -20,7 +20,6 @@ public class CollectorConfigDTO { private Long moduleId; @NotNull - @NotEmpty @UniqueServerName private List keys; From 44e543d25afd60ae5b2dd81a4dc79ce5194e44c1 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 16 Feb 2026 11:48:23 -0600 Subject: [PATCH 081/240] feat(vmware-esxi-filter): add update for VMware ESXi filter with enhanced parsing and cleanup --- .../20260216001_update_filter_vmware_esxi.xml | 157 ++++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 2 files changed, 159 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260216001_update_filter_vmware_esxi.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260216001_update_filter_vmware_esxi.xml b/backend/src/main/resources/config/liquibase/changelog/20260216001_update_filter_vmware_esxi.xml new file mode 100644 index 000000000..941f18aa3 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260216001_update_filter_vmware_esxi.xml @@ -0,0 +1,157 @@ + + + + + + + ' + - fieldName: log.deviceTime + pattern: '{{.year}}(-){{.monthNumber}}(-){{.monthDay}}(T){{.time}}(Z)' + - fieldName: origin.hostname + pattern: '{{.hostname}}' + - fieldName: log.process + pattern: '{{.hostname}}(\:)' + - fieldName: severity + pattern: '{{.word}}' + - fieldName: log.processName + pattern: '{{.hostname}}' + - fieldName: log.pid + pattern: '\[{{.data}}\]' + - fieldName: log.eventInfo + pattern: '\[{{.data}}\]' + - fieldName: log.message + pattern: '{{.greedy}}' + + - grok: + patterns: + - fieldName: log.priority + pattern: '\<{{.data}}\>' + - fieldName: log.deviceTime + pattern: '{{.year}}(-){{.monthNumber}}(-){{.monthDay}}(T){{.time}}(Z)' + - fieldName: origin.hostname + pattern: '{{.hostname}}' + - fieldName: log.process + pattern: '{{.hostname}}' + - fieldName: log.pid + pattern: '\[{{.data}}\]:' + - fieldName: log.message + pattern: '{{.greedy}}' + + - grok: + patterns: + - fieldName: log.priority + pattern: '\<{{.data}}\>' + - fieldName: log.deviceTime + pattern: '{{.year}}-{{.monthNumber}}-{{.monthDay}}T{{.time}}Z' + - fieldName: origin.hostname + pattern: '{{.hostname}}' + - fieldName: log.process + pattern: '{{.hostname}}' + - fieldName: log.pid + pattern: '\[{{.data}}\]:' + - fieldName: log.originIdComponent + pattern: '\[{{.data}}\]' + - fieldName: log.message + pattern: '{{.greedy}}' + + - grok: + patterns: + - fieldName: log.moduleIdentifier + pattern: '\[{{.data}}\@' + - fieldName: log.irrelevant + pattern: '{{.data}}\=' + - fieldName: log.subModuleIdentifier + pattern: '{{.word}}\]' + source: log.originIdComponent + + - grok: + patterns: + - fieldName: log.irrelevant2 + pattern: '{{.data}}level{{.space}}=' + - fieldName: log.level + pattern: '{{.integer}}' + source: log.message + + # Removing unused caracters + - trim: + function: prefix + substring: '<' + fields: + - log.priority + - trim: + function: prefix + substring: '[' + fields: + - log.pid + - log.eventInfo + - log.moduleIdentifier + - trim: + function: prefix + substring: '-' + fields: + - log.message + - trim: + function: suffix + substring: '>' + fields: + - log.priority + - trim: + function: suffix + substring: ':' + fields: + - log.pid + - log.process + - trim: + function: suffix + substring: ']' + fields: + - log.pid + - log.eventInfo + - log.subModuleIdentifier + - trim: + function: suffix + substring: '-' + fields: + - log.message + - trim: + function: suffix + substring: '@' + fields: + - log.moduleIdentifier + + # Removing unused fields + - delete: + fields: + - log.processName + - log.irrelevant$$ + WHERE id = 1001; + ]]> + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index f559743dd..8fd4434f5 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -431,6 +431,8 @@ + + From a41a5f9a22ab221b9b3bf9240c474f67f65972d5 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 16 Feb 2026 11:58:13 -0600 Subject: [PATCH 082/240] feat(collector): enhance save button behavior and loading state in group configuration Signed-off-by: Manuel Abascal --- .../int-generic-group-config.component.html | 4 ++-- .../int-generic-group-config.component.ts | 1 + .../int-create-group/int-create-group.component.html | 2 +- .../int-create-group/int-create-group.component.ts | 6 ++---- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.html b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.html index 094d1cc75..54b7a5fe9 100644 --- a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.html +++ b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.html @@ -42,8 +42,8 @@ [disabled]="(!collectorValid(collector.groups) || savingConfig) || (collectorValid(collector.groups) && changes && changes.keys.length === 0) || (!pendingChangesForCollector(collector.groups))"> - - Save collector + + Save configuration
diff --git a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts index 399158384..0ec4f106b 100644 --- a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts +++ b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts @@ -331,6 +331,7 @@ export class IntGenericGroupConfigComponent implements OnInit, OnChanges, OnDest } saveCollectorConfig(collector: any, action = 'CREATE') { + this.savingConfig = true; (this.config as CollectorConfiguration).saveCollector(collector) .subscribe(response => { this.savingConfig = false; diff --git a/frontend/src/app/app-module/shared/components/int-create-group/int-create-group.component.html b/frontend/src/app/app-module/shared/components/int-create-group/int-create-group.component.html index 5bc08e691..dc1ed24db 100644 --- a/frontend/src/app/app-module/shared/components/int-create-group/int-create-group.component.html +++ b/frontend/src/app/app-module/shared/components/int-create-group/int-create-group.component.html @@ -39,7 +39,7 @@ Cancel
-

- The Linux agent captures system and application logs and sends them to a UTMStack master server or probe/proxy, - monitors system activity, and executes incident response commands. The UTMStack agents communicate over ports 9000, 9001 and 50051. - Please make sure these ports are open. -

- -
    +
    1. 1 Check pre-installation requirements

        -
      • The UTMStack Linux Agent relies on the local rsyslog service to collect and forward system logs.
      • +
      • The UTMStack agents communicate over ports 9000, 9001 and 50051. Please make sure these ports are open.
    2. @@ -30,7 +24,9 @@

      The following commands contains sensitive information, don't share it.
      - + +

diff --git a/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.ts b/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.ts index a143e9ed1..949b26fd3 100644 --- a/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.ts +++ b/frontend/src/app/app-module/guides/guide-linux-agent/guide-linux-agent.component.ts @@ -13,7 +13,7 @@ export class GuideLinuxAgentComponent implements OnInit { @Input() serverId: number; @Input() version: string; token: string; - architectures = []; + platforms = []; constructor(private federationConnectionService: FederationConnectionService) { } @@ -21,7 +21,6 @@ export class GuideLinuxAgentComponent implements OnInit { this.getToken(); } - getToken() { this.federationConnectionService.getToken().subscribe(response => { if (response.body !== null && response.body !== '') { @@ -29,7 +28,7 @@ export class GuideLinuxAgentComponent implements OnInit { } else { this.token = ''; } - this.loadArchitectures(); + this.loadPlatforms(); }); } @@ -75,26 +74,35 @@ export class GuideLinuxAgentComponent implements OnInit { echo 'UTMStack Agent dependencies removed successfully.'"`; } - private loadArchitectures() { - this.architectures = [ + private loadPlatforms() { + const amd64 = 'utmstack_agent_service_linux_amd64'; + const arm64 = 'utmstack_agent_service_linux_arm64'; + + this.platforms = [ { - id: 1, name: 'Ubuntu / Debian', - install: this.getCommandUbuntu('utmstack_agent_service'), - uninstall: this.getUninstallCommand('utmstack_agent_service'), + id: 1, name: 'Ubuntu / Debian (AMD64)', + install: this.getCommandUbuntu(amd64), + uninstall: this.getUninstallCommand(amd64), shell: '' }, { - id: 2, name: 'Fedora / RedHat', - install: this.getCommandCentos7RedHat('utmstack_agent_service'), - uninstall: this.getUninstallCommand('utmstack_agent_service'), + id: 2, name: 'Ubuntu / Debian (ARM64)', + install: this.getCommandUbuntu(arm64), + uninstall: this.getUninstallCommand(arm64), shell: '' - }/*, + }, { - id: 3, name: 'Centos 8/AlmaLinux', - install: this.getCommandCentos8Almalinux('utmstack_agent_service'), - uninstall: this.getUninstallCommand('utmstack_agent_service'), + id: 3, name: 'Fedora / RedHat (AMD64)', + install: this.getCommandCentos7RedHat(amd64), + uninstall: this.getUninstallCommand(amd64), shell: '' - }*/ + }, + { + id: 4, name: 'Fedora / RedHat (ARM64)', + install: this.getCommandCentos7RedHat(arm64), + uninstall: this.getUninstallCommand(arm64), + shell: '' + } ]; } } diff --git a/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.html b/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.html index 6114adab4..6e1874cf8 100644 --- a/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.html +++ b/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.html @@ -1,33 +1,30 @@

- Installing MacOS agent + Installing macOS agent

-

- The MacOS agent captures system and application logs and sends them to a UTMStack master server or probe/proxy, monitors system activity, and executes incident response commands. - The UTMStack agents communicate over ports 9000, 9001 and 50051. Please make sure these ports are open. -

  1. 1 - Download the utmstack-macos-agent.pkg file. + Check pre-installation requirements

    +
      +
    • Compatible with macOS 11 (Big Sur) or higher on Apple Silicon (M1/M2/M3)
    • +
    • The UTMStack agents communicate over ports 9000, 9001 and 50051. Please make sure these ports are open.
    • +
  2. 2 - Run the utmstack-macos-agent.pkg file. This will download and install the required components for the UTMStack agent. + To install or uninstall the UTMStack agent, open a Terminal and run the following command:

    -
  3. -
  4. -

    - 3 - Use the following command according to the action you wish to perform (install or uninstall): -

    - +
    + The following command contains sensitive information, don't share it. +
    +
diff --git a/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.ts b/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.ts index 01ef208fb..64841d6ee 100644 --- a/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.ts +++ b/frontend/src/app/app-module/guides/guide-macos-agent/guide-macos-agent.component.ts @@ -11,20 +11,18 @@ import { }) export class GuideMacosAgentComponent implements OnInit { @Input() integrationId: number; - @Input() filebeatModule: UtmModulesEnum; - @Input() filebeatModuleName: string; module = UtmModulesEnum; @Input() serverId: number; - architectures = []; + platforms = []; token: string; - constructor(private federationConnectionService: FederationConnectionService,) { } + + constructor(private federationConnectionService: FederationConnectionService) { } ngOnInit() { this.getToken(); } - getToken() { this.federationConnectionService.getToken().subscribe(response => { if (response.body !== null && response.body !== '') { @@ -32,35 +30,41 @@ export class GuideMacosAgentComponent implements OnInit { } else { this.token = ''; } - this.loadArchitectures(); + this.loadPlatforms(); }); } - getCommandARM(installerName: string): string { + getInstallCommand(installerName: string): string { const ip = window.location.host.includes(':') ? window.location.host.split(':')[0] : window.location.host; - return `sudo bash -c "/opt/utmstack/${installerName} ${ip} ${this.token} yes"`; + return `sudo bash -c "mkdir -p /opt/utmstack-macos-agent && \\ +curl -k -o /opt/utmstack-macos-agent/${installerName} \\ +https://${ip}:9001/private/dependencies/agent/${installerName} && \\ +chmod +x /opt/utmstack-macos-agent/${installerName} && \\ +/opt/utmstack-macos-agent/${installerName} install ${ip} ${this.token} yes"`; } - getUninstallCommand(installerName: string): string { - // tslint:disable-next-line:max-line-length - return `sudo bash -c "/opt/utmstack/${installerName}; launchctl bootout system /Library/LaunchDaemons/UTMStackAgent.plist 2>/dev/null; rm /Library/LaunchDaemons/UTMStackAgent.plist; rm -rf /opt/utmstack"`; + return `sudo bash -c "/opt/utmstack-macos-agent/${installerName} uninstall || true; \\ +launchctl bootout system /Library/LaunchDaemons/UTMStackAgent.plist 2>/dev/null || true; \\ +launchctl bootout system /Library/LaunchDaemons/UTMStackUpdater.plist 2>/dev/null || true; \\ +rm -f /Library/LaunchDaemons/UTMStackAgent.plist 2>/dev/null || true; \\ +rm -f /Library/LaunchDaemons/UTMStackUpdater.plist 2>/dev/null || true; \\ +echo 'Removing UTMStack Agent dependencies...' && sleep 10 && \\ +rm -rf /opt/utmstack-macos-agent 2>/dev/null || true; \\ +echo 'UTMStack Agent removed successfully.'"`; } - getDownloadUrl(): string { - const ip = window.location.host.includes(':') ? window.location.host.split(':')[0] : window.location.host; - return `https://${ip}:9001/private/dependencies/agent/utmstack-macos-agent.pkg`; - } + private loadPlatforms() { + const arm64 = 'utmstack_agent_service_darwin_arm64'; - private loadArchitectures() { - this.architectures = [ + this.platforms = [ { - id: 1, name: 'ARM64', - install: this.getCommandARM('utmstack_agent_service install'), - uninstall: this.getUninstallCommand('utmstack_agent_service uninstall'), + id: 1, name: 'ARM64 (Apple Silicon)', + install: this.getInstallCommand(arm64), + uninstall: this.getUninstallCommand(arm64), shell: '' - }, + } ]; } } diff --git a/frontend/src/app/app-module/guides/guide-winlogbeat/guide-winlogbeat.component.ts b/frontend/src/app/app-module/guides/guide-winlogbeat/guide-winlogbeat.component.ts index bca0c2878..114a38ff7 100644 --- a/frontend/src/app/app-module/guides/guide-winlogbeat/guide-winlogbeat.component.ts +++ b/frontend/src/app/app-module/guides/guide-winlogbeat/guide-winlogbeat.component.ts @@ -39,15 +39,15 @@ export class GuideWinlogbeatComponent implements OnInit { this.architectures = [ { id: 1, name: 'AMD64', - install: this.getCommand('utmstack_agent_service.exe'), - uninstall: this.getUninstallCommand('utmstack_agent_service.exe'), - shell: 'Windows Powershell terminal as “ADMINISTRATOR”' + install: this.getCommand('utmstack_agent_service_windows_amd64.exe'), + uninstall: this.getUninstallCommand('utmstack_agent_service_windows_amd64.exe'), + shell: 'Windows Powershell terminal as "ADMINISTRATOR"' }, { id: 2, name: 'ARM64', - install: this.getCommand('utmstack_agent_service_arm64.exe'), - uninstall: this.getUninstallCommand('utmstack_agent_service_arm64.exe'), - shell: 'Windows Powershell terminal as “ADMINISTRATOR”' + install: this.getCommand('utmstack_agent_service_windows_arm64.exe'), + uninstall: this.getUninstallCommand('utmstack_agent_service_windows_arm64.exe'), + shell: 'Windows Powershell terminal as "ADMINISTRATOR"' } ]; } diff --git a/frontend/src/app/app-module/guides/shared/components/agent-install-selector.component.ts b/frontend/src/app/app-module/guides/shared/components/agent-install-selector.component.ts index 6954ce64c..f1e6c50a7 100644 --- a/frontend/src/app/app-module/guides/shared/components/agent-install-selector.component.ts +++ b/frontend/src/app/app-module/guides/shared/components/agent-install-selector.component.ts @@ -11,7 +11,7 @@ import {UtmModulesEnum} from '../../../shared/enum/utm-module.enum';
From e220685c4ba39589b063f1c4142405667eca98d8 Mon Sep 17 00:00:00 2001 From: Yorjander Hernandez Vergara Date: Wed, 18 Feb 2026 03:40:45 -0500 Subject: [PATCH 101/240] fix(agent-manager): correct FilterScope to properly chain WHERE clauses and fix LIKE syntax --- agent-manager/utils/filter.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/agent-manager/utils/filter.go b/agent-manager/utils/filter.go index c0b403fb0..dbb7f960a 100644 --- a/agent-manager/utils/filter.go +++ b/agent-manager/utils/filter.go @@ -75,17 +75,17 @@ func FilterScope(filters []Filter) func(db *gorm.DB) *gorm.DB { for _, filter := range filters { switch filter.Op { case Is: - db.Where(filter.Field+" = ?", filter.Value) + db = db.Where(filter.Field+" = ?", filter.Value) case IsNot: - db.Where(filter.Field+" <> ?", filter.Value) + db = db.Where(filter.Field+" <> ?", filter.Value) case Contain: - db.Where(filter.Field+" like %?%", filter.Value) + db = db.Where(filter.Field+" LIKE ?", "%"+fmt.Sprintf("%v", filter.Value)+"%") case NotContain: - db.Where(filter.Field+" not like %?%", filter.Value) + db = db.Where(filter.Field+" NOT LIKE ?", "%"+fmt.Sprintf("%v", filter.Value)+"%") case In: - db.Where(filter.Field+" in (?)", filter.Value) + db = db.Where(filter.Field+" IN (?)", filter.Value) case NotIn: - db.Where(filter.Field+" not in (?)", filter.Value) + db = db.Where(filter.Field+" NOT IN (?)", filter.Value) } } return db From 9e56840e5a7f3ab79b817fe99cfede4f9e81fc96 Mon Sep 17 00:00:00 2001 From: Yorjander Hernandez Vergara Date: Wed, 18 Feb 2026 03:53:52 -0500 Subject: [PATCH 102/240] feat(agent): add shell selection for command execution and fix agent registration --- agent-manager/agent/agent.pb.go | 604 ++++++++--------------- agent-manager/agent/agent_grpc.pb.go | 2 +- agent-manager/agent/agent_imp.go | 17 +- agent-manager/agent/collector.pb.go | 6 +- agent-manager/agent/collector_grpc.pb.go | 2 +- agent-manager/agent/common.pb.go | 162 +++--- agent-manager/agent/ping.pb.go | 103 ++-- agent-manager/agent/ping_grpc.pb.go | 2 +- agent-manager/protos/agent.proto | 1 + agent/agent/agent.pb.go | 604 ++++++++--------------- agent/agent/agent_grpc.pb.go | 2 +- agent/agent/common.pb.go | 162 +++--- agent/agent/incident_response.go | 26 +- agent/agent/ping.pb.go | 103 ++-- agent/agent/ping_grpc.pb.go | 2 +- agent/protos/agent.proto | 2 + agent/version.json | 4 +- 17 files changed, 638 insertions(+), 1166 deletions(-) diff --git a/agent-manager/agent/agent.pb.go b/agent-manager/agent/agent.pb.go index 7c73da984..d5b52edb9 100644 --- a/agent-manager/agent/agent.pb.go +++ b/agent-manager/agent/agent.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: agent.proto package agent @@ -12,6 +12,7 @@ import ( timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -77,30 +78,27 @@ func (AgentCommandStatus) EnumDescriptor() ([]byte, []int) { } type AgentRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` - Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` - Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` - Platform string `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"` - Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` - RegisterBy string `protobuf:"bytes,6,opt,name=register_by,json=registerBy,proto3" json:"register_by,omitempty"` - Mac string `protobuf:"bytes,7,opt,name=mac,proto3" json:"mac,omitempty"` - OsMajorVersion string `protobuf:"bytes,8,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` - OsMinorVersion string `protobuf:"bytes,9,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` - Aliases string `protobuf:"bytes,10,opt,name=aliases,proto3" json:"aliases,omitempty"` - Addresses string `protobuf:"bytes,11,opt,name=addresses,proto3" json:"addresses,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` + Platform string `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"` + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + RegisterBy string `protobuf:"bytes,6,opt,name=register_by,json=registerBy,proto3" json:"register_by,omitempty"` + Mac string `protobuf:"bytes,7,opt,name=mac,proto3" json:"mac,omitempty"` + OsMajorVersion string `protobuf:"bytes,8,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` + OsMinorVersion string `protobuf:"bytes,9,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` + Aliases string `protobuf:"bytes,10,opt,name=aliases,proto3" json:"aliases,omitempty"` + Addresses string `protobuf:"bytes,11,opt,name=addresses,proto3" json:"addresses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentRequest) Reset() { *x = AgentRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentRequest) String() string { @@ -111,7 +109,7 @@ func (*AgentRequest) ProtoMessage() {} func (x *AgentRequest) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -204,21 +202,18 @@ func (x *AgentRequest) GetAddresses() string { } type ListAgentsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Rows []*Agent `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` unknownFields protoimpl.UnknownFields - - Rows []*Agent `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` - Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListAgentsResponse) Reset() { *x = ListAgentsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListAgentsResponse) String() string { @@ -229,7 +224,7 @@ func (*ListAgentsResponse) ProtoMessage() {} func (x *ListAgentsResponse) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -259,33 +254,30 @@ func (x *ListAgentsResponse) GetTotal() int32 { } type Agent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` - Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` - Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` - Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=agent.Status" json:"status,omitempty"` - Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` - Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` - AgentKey string `protobuf:"bytes,7,opt,name=agent_key,json=agentKey,proto3" json:"agent_key,omitempty"` - Id uint32 `protobuf:"varint,8,opt,name=id,proto3" json:"id,omitempty"` - LastSeen string `protobuf:"bytes,9,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` - Mac string `protobuf:"bytes,10,opt,name=mac,proto3" json:"mac,omitempty"` - OsMajorVersion string `protobuf:"bytes,11,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` - OsMinorVersion string `protobuf:"bytes,12,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` - Aliases string `protobuf:"bytes,13,opt,name=aliases,proto3" json:"aliases,omitempty"` - Addresses string `protobuf:"bytes,14,opt,name=addresses,proto3" json:"addresses,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` + Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=agent.Status" json:"status,omitempty"` + Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` + Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` + AgentKey string `protobuf:"bytes,7,opt,name=agent_key,json=agentKey,proto3" json:"agent_key,omitempty"` + Id uint32 `protobuf:"varint,8,opt,name=id,proto3" json:"id,omitempty"` + LastSeen string `protobuf:"bytes,9,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` + Mac string `protobuf:"bytes,10,opt,name=mac,proto3" json:"mac,omitempty"` + OsMajorVersion string `protobuf:"bytes,11,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` + OsMinorVersion string `protobuf:"bytes,12,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` + Aliases string `protobuf:"bytes,13,opt,name=aliases,proto3" json:"aliases,omitempty"` + Addresses string `protobuf:"bytes,14,opt,name=addresses,proto3" json:"addresses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Agent) Reset() { *x = Agent{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Agent) String() string { @@ -296,7 +288,7 @@ func (*Agent) ProtoMessage() {} func (x *Agent) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -410,24 +402,21 @@ func (x *Agent) GetAddresses() string { } type BidirectionalStream struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to StreamMessage: + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to StreamMessage: // // *BidirectionalStream_Command // *BidirectionalStream_Result StreamMessage isBidirectionalStream_StreamMessage `protobuf_oneof:"stream_message"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *BidirectionalStream) Reset() { *x = BidirectionalStream{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BidirectionalStream) String() string { @@ -438,7 +427,7 @@ func (*BidirectionalStream) ProtoMessage() {} func (x *BidirectionalStream) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -453,23 +442,27 @@ func (*BidirectionalStream) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{3} } -func (m *BidirectionalStream) GetStreamMessage() isBidirectionalStream_StreamMessage { - if m != nil { - return m.StreamMessage +func (x *BidirectionalStream) GetStreamMessage() isBidirectionalStream_StreamMessage { + if x != nil { + return x.StreamMessage } return nil } func (x *BidirectionalStream) GetCommand() *UtmCommand { - if x, ok := x.GetStreamMessage().(*BidirectionalStream_Command); ok { - return x.Command + if x != nil { + if x, ok := x.StreamMessage.(*BidirectionalStream_Command); ok { + return x.Command + } } return nil } func (x *BidirectionalStream) GetResult() *CommandResult { - if x, ok := x.GetStreamMessage().(*BidirectionalStream_Result); ok { - return x.Result + if x != nil { + if x, ok := x.StreamMessage.(*BidirectionalStream_Result); ok { + return x.Result + } } return nil } @@ -491,26 +484,24 @@ func (*BidirectionalStream_Command) isBidirectionalStream_StreamMessage() {} func (*BidirectionalStream_Result) isBidirectionalStream_StreamMessage() {} type UtmCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` + ExecutedBy string `protobuf:"bytes,3,opt,name=executed_by,json=executedBy,proto3" json:"executed_by,omitempty"` + CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` + OriginType string `protobuf:"bytes,5,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` + OriginId string `protobuf:"bytes,6,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` + Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` + Shell string `protobuf:"bytes,8,opt,name=shell,proto3" json:"shell,omitempty"` // Shell to execute command: "cmd", "powershell" (Windows), "sh", "bash" (Linux/macOS). Empty = default unknownFields protoimpl.UnknownFields - - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` - ExecutedBy string `protobuf:"bytes,3,opt,name=executed_by,json=executedBy,proto3" json:"executed_by,omitempty"` - CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` - OriginType string `protobuf:"bytes,5,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` - OriginId string `protobuf:"bytes,6,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` - Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` + sizeCache protoimpl.SizeCache } func (x *UtmCommand) Reset() { *x = UtmCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UtmCommand) String() string { @@ -521,7 +512,7 @@ func (*UtmCommand) ProtoMessage() {} func (x *UtmCommand) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -585,24 +576,28 @@ func (x *UtmCommand) GetReason() string { return "" } +func (x *UtmCommand) GetShell() string { + if x != nil { + return x.Shell + } + return "" +} + type CommandResult struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` + ExecutedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=executed_at,json=executedAt,proto3" json:"executed_at,omitempty"` + CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` unknownFields protoimpl.UnknownFields - - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` - ExecutedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=executed_at,json=executedAt,proto3" json:"executed_at,omitempty"` - CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *CommandResult) Reset() { *x = CommandResult{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CommandResult) String() string { @@ -613,7 +608,7 @@ func (*CommandResult) ProtoMessage() {} func (x *CommandResult) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -657,21 +652,18 @@ func (x *CommandResult) GetCmdId() string { } type ListAgentsCommandsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Rows []*AgentCommand `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` unknownFields protoimpl.UnknownFields - - Rows []*AgentCommand `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` - Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListAgentsCommandsResponse) Reset() { *x = ListAgentsCommandsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListAgentsCommandsResponse) String() string { @@ -682,7 +674,7 @@ func (*ListAgentsCommandsResponse) ProtoMessage() {} func (x *ListAgentsCommandsResponse) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -712,10 +704,7 @@ func (x *ListAgentsCommandsResponse) GetTotal() int32 { } type AgentCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` CreatedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` AgentId uint32 `protobuf:"varint,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` @@ -727,15 +716,15 @@ type AgentCommand struct { Reason string `protobuf:"bytes,9,opt,name=reason,proto3" json:"reason,omitempty"` OriginType string `protobuf:"bytes,10,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` OriginId string `protobuf:"bytes,11,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentCommand) Reset() { *x = AgentCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentCommand) String() string { @@ -746,7 +735,7 @@ func (*AgentCommand) ProtoMessage() {} func (x *AgentCommand) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -840,182 +829,116 @@ func (x *AgentCommand) GetOriginId() string { var File_agent_proto protoreflect.FileDescriptor -var file_agent_proto_rawDesc = []byte{ - 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xbf, 0x02, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x42, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x63, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, - 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, - 0x73, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, - 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x4c, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x04, 0x72, - 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x14, 0x0a, - 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x22, 0x88, 0x03, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1a, 0x0a, - 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x4b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, - 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, - 0x61, 0x63, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, - 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, - 0x6f, 0x73, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, - 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, - 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x86, - 0x01, 0x0a, 0x13, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x55, 0x74, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x2e, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x10, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xcf, 0x01, 0x0a, 0x0a, 0x55, 0x74, 0x6d, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x15, 0x0a, 0x06, - 0x63, 0x6d, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, - 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x96, 0x01, 0x0a, 0x0d, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x3b, - 0x0a, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x63, - 0x6d, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, 0x64, - 0x49, 0x64, 0x22, 0x5b, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x27, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, - 0xa1, 0x03, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x40, 0x0a, 0x0e, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0d, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, - 0x64, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x63, 0x6d, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, - 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x69, 0x67, 0x69, - 0x6e, 0x49, 0x64, 0x2a, 0x57, 0x0a, 0x12, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x4f, 0x54, - 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x51, - 0x55, 0x45, 0x55, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, - 0x47, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x10, - 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x32, 0x9c, 0x03, 0x0a, - 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, - 0x0d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0b, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x3d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, - 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x4b, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, - 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x1a, 0x1a, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, - 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4c, 0x0a, - 0x11, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x73, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x4f, 0x0a, 0x0c, 0x50, - 0x61, 0x6e, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x50, - 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x11, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x74, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x1a, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x32, 0x5a, 0x30, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_agent_proto_rawDesc = "" + + "\n" + + "\vagent.proto\x12\x05agent\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\fcommon.proto\"\xbf\x02\n" + + "\fAgentRequest\x12\x0e\n" + + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12\x0e\n" + + "\x02os\x18\x03 \x01(\tR\x02os\x12\x1a\n" + + "\bplatform\x18\x04 \x01(\tR\bplatform\x12\x18\n" + + "\aversion\x18\x05 \x01(\tR\aversion\x12\x1f\n" + + "\vregister_by\x18\x06 \x01(\tR\n" + + "registerBy\x12\x10\n" + + "\x03mac\x18\a \x01(\tR\x03mac\x12(\n" + + "\x10os_major_version\x18\b \x01(\tR\x0eosMajorVersion\x12(\n" + + "\x10os_minor_version\x18\t \x01(\tR\x0eosMinorVersion\x12\x18\n" + + "\aaliases\x18\n" + + " \x01(\tR\aaliases\x12\x1c\n" + + "\taddresses\x18\v \x01(\tR\taddresses\"L\n" + + "\x12ListAgentsResponse\x12 \n" + + "\x04rows\x18\x01 \x03(\v2\f.agent.AgentR\x04rows\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"\x88\x03\n" + + "\x05Agent\x12\x0e\n" + + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12\x0e\n" + + "\x02os\x18\x03 \x01(\tR\x02os\x12%\n" + + "\x06status\x18\x04 \x01(\x0e2\r.agent.StatusR\x06status\x12\x1a\n" + + "\bplatform\x18\x05 \x01(\tR\bplatform\x12\x18\n" + + "\aversion\x18\x06 \x01(\tR\aversion\x12\x1b\n" + + "\tagent_key\x18\a \x01(\tR\bagentKey\x12\x0e\n" + + "\x02id\x18\b \x01(\rR\x02id\x12\x1b\n" + + "\tlast_seen\x18\t \x01(\tR\blastSeen\x12\x10\n" + + "\x03mac\x18\n" + + " \x01(\tR\x03mac\x12(\n" + + "\x10os_major_version\x18\v \x01(\tR\x0eosMajorVersion\x12(\n" + + "\x10os_minor_version\x18\f \x01(\tR\x0eosMinorVersion\x12\x18\n" + + "\aaliases\x18\r \x01(\tR\aaliases\x12\x1c\n" + + "\taddresses\x18\x0e \x01(\tR\taddresses\"\x86\x01\n" + + "\x13BidirectionalStream\x12-\n" + + "\acommand\x18\x01 \x01(\v2\x11.agent.UtmCommandH\x00R\acommand\x12.\n" + + "\x06result\x18\x02 \x01(\v2\x14.agent.CommandResultH\x00R\x06resultB\x10\n" + + "\x0estream_message\"\xe5\x01\n" + + "\n" + + "UtmCommand\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x18\n" + + "\acommand\x18\x02 \x01(\tR\acommand\x12\x1f\n" + + "\vexecuted_by\x18\x03 \x01(\tR\n" + + "executedBy\x12\x15\n" + + "\x06cmd_id\x18\x04 \x01(\tR\x05cmdId\x12\x1f\n" + + "\vorigin_type\x18\x05 \x01(\tR\n" + + "originType\x12\x1b\n" + + "\torigin_id\x18\x06 \x01(\tR\boriginId\x12\x16\n" + + "\x06reason\x18\a \x01(\tR\x06reason\x12\x14\n" + + "\x05shell\x18\b \x01(\tR\x05shell\"\x96\x01\n" + + "\rCommandResult\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x16\n" + + "\x06result\x18\x02 \x01(\tR\x06result\x12;\n" + + "\vexecuted_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "executedAt\x12\x15\n" + + "\x06cmd_id\x18\x04 \x01(\tR\x05cmdId\"[\n" + + "\x1aListAgentsCommandsResponse\x12'\n" + + "\x04rows\x18\x01 \x03(\v2\x13.agent.AgentCommandR\x04rows\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"\xa1\x03\n" + + "\fAgentCommand\x129\n" + + "\n" + + "created_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "updated_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x19\n" + + "\bagent_id\x18\x03 \x01(\rR\aagentId\x12\x18\n" + + "\acommand\x18\x04 \x01(\tR\acommand\x12@\n" + + "\x0ecommand_status\x18\x05 \x01(\x0e2\x19.agent.AgentCommandStatusR\rcommandStatus\x12\x16\n" + + "\x06result\x18\x06 \x01(\tR\x06result\x12\x1f\n" + + "\vexecuted_by\x18\a \x01(\tR\n" + + "executedBy\x12\x15\n" + + "\x06cmd_id\x18\b \x01(\tR\x05cmdId\x12\x16\n" + + "\x06reason\x18\t \x01(\tR\x06reason\x12\x1f\n" + + "\vorigin_type\x18\n" + + " \x01(\tR\n" + + "originType\x12\x1b\n" + + "\torigin_id\x18\v \x01(\tR\boriginId*W\n" + + "\x12AgentCommandStatus\x12\x10\n" + + "\fNOT_EXECUTED\x10\x00\x12\t\n" + + "\x05QUEUE\x10\x01\x12\v\n" + + "\aPENDING\x10\x02\x12\f\n" + + "\bEXECUTED\x10\x03\x12\t\n" + + "\x05ERROR\x10\x042\x9c\x03\n" + + "\fAgentService\x12;\n" + + "\rRegisterAgent\x12\x13.agent.AgentRequest\x1a\x13.agent.AuthResponse\"\x00\x129\n" + + "\vUpdateAgent\x12\x13.agent.AgentRequest\x1a\x13.agent.AuthResponse\"\x00\x12:\n" + + "\vDeleteAgent\x12\x14.agent.DeleteRequest\x1a\x13.agent.AuthResponse\"\x00\x12=\n" + + "\n" + + "ListAgents\x12\x12.agent.ListRequest\x1a\x19.agent.ListAgentsResponse\"\x00\x12K\n" + + "\vAgentStream\x12\x1a.agent.BidirectionalStream\x1a\x1a.agent.BidirectionalStream\"\x00(\x010\x01\x12L\n" + + "\x11ListAgentCommands\x12\x12.agent.ListRequest\x1a!.agent.ListAgentsCommandsResponse\"\x002O\n" + + "\fPanelService\x12?\n" + + "\x0eProcessCommand\x12\x11.agent.UtmCommand\x1a\x14.agent.CommandResult\"\x00(\x010\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_agent_proto_rawDescOnce sync.Once - file_agent_proto_rawDescData = file_agent_proto_rawDesc + file_agent_proto_rawDescData []byte ) func file_agent_proto_rawDescGZIP() []byte { file_agent_proto_rawDescOnce.Do(func() { - file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_proto_rawDescData) + file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc))) }) return file_agent_proto_rawDescData } var file_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 8) -var file_agent_proto_goTypes = []interface{}{ +var file_agent_proto_goTypes = []any{ (AgentCommandStatus)(0), // 0: agent.AgentCommandStatus (*AgentRequest)(nil), // 1: agent.AgentRequest (*ListAgentsResponse)(nil), // 2: agent.ListAgentsResponse @@ -1068,105 +991,7 @@ func file_agent_proto_init() { return } file_common_proto_init() - if !protoimpl.UnsafeEnabled { - file_agent_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAgentsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Agent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BidirectionalStream); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UtmCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CommandResult); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAgentsCommandsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_agent_proto_msgTypes[3].OneofWrappers = []interface{}{ + file_agent_proto_msgTypes[3].OneofWrappers = []any{ (*BidirectionalStream_Command)(nil), (*BidirectionalStream_Result)(nil), } @@ -1174,7 +999,7 @@ func file_agent_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_agent_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc)), NumEnums: 1, NumMessages: 8, NumExtensions: 0, @@ -1186,7 +1011,6 @@ func file_agent_proto_init() { MessageInfos: file_agent_proto_msgTypes, }.Build() File_agent_proto = out.File - file_agent_proto_rawDesc = nil file_agent_proto_goTypes = nil file_agent_proto_depIdxs = nil } diff --git a/agent-manager/agent/agent_grpc.pb.go b/agent-manager/agent/agent_grpc.pb.go index f75ba57b2..400673e17 100644 --- a/agent-manager/agent/agent_grpc.pb.go +++ b/agent-manager/agent/agent_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 +// - protoc v5.29.3 // source: agent.proto package agent diff --git a/agent-manager/agent/agent_imp.go b/agent-manager/agent/agent_imp.go index d9e09b870..fafa7d97d 100644 --- a/agent-manager/agent/agent_imp.go +++ b/agent-manager/agent/agent_imp.go @@ -75,17 +75,13 @@ func (s *AgentService) RegisterAgent(ctx context.Context, req *AgentRequest) (*A } oldAgent := &models.Agent{} - err := s.DBConnection.GetFirst(oldAgent, "hostname = ?", agent.Hostname) + err := s.DBConnection.GetFirst(oldAgent, "hostname = ? AND mac = ?", agent.Hostname, agent.Mac) if err == nil { - if oldAgent.Ip == agent.Ip { - return &AuthResponse{ - Id: uint32(oldAgent.ID), - Key: oldAgent.AgentKey, - }, nil - } else { - catcher.Error("agent already exists", err, map[string]any{"hostname": agent.Hostname, "process": "agent-manager"}) - return nil, status.Errorf(codes.AlreadyExists, "hostname has already been registered") - } + // Same machine re-registering, return existing agent + return &AuthResponse{ + Id: uint32(oldAgent.ID), + Key: oldAgent.AgentKey, + }, nil } key := uuid.New().String() @@ -334,6 +330,7 @@ func (s *AgentService) ProcessCommand(stream PanelService_ProcessCommandServer) AgentId: cmd.AgentId, Command: replaceSecretValues(cmd.Command), CmdId: cmdID, + Shell: cmd.Shell, }, }, }) diff --git a/agent-manager/agent/collector.pb.go b/agent-manager/agent/collector.pb.go index bcf8d63b7..be2fe7fa6 100644 --- a/agent-manager/agent/collector.pb.go +++ b/agent-manager/agent/collector.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.9 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: collector.proto package agent @@ -766,7 +766,7 @@ const file_collector_proto_rawDesc = "" + "\x0fCollectorStream\x12\x18.agent.CollectorMessages\x1a\x18.agent.CollectorMessages\"\x00(\x010\x01\x12D\n" + "\x12GetCollectorConfig\x12\x14.agent.ConfigRequest\x1a\x16.agent.CollectorConfig\"\x002d\n" + "\x15PanelCollectorService\x12K\n" + - "\x17RegisterCollectorConfig\x12\x16.agent.CollectorConfig\x1a\x16.agent.ConfigKnowledge\"\x00B5Z3github.com/utmstack/UTMStack/docker-collector/agentb\x06proto3" + "\x17RegisterCollectorConfig\x12\x16.agent.CollectorConfig\x1a\x16.agent.ConfigKnowledge\"\x00B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_collector_proto_rawDescOnce sync.Once diff --git a/agent-manager/agent/collector_grpc.pb.go b/agent-manager/agent/collector_grpc.pb.go index a924c7361..307c97b51 100644 --- a/agent-manager/agent/collector_grpc.pb.go +++ b/agent-manager/agent/collector_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 +// - protoc v5.29.3 // source: collector.proto package agent diff --git a/agent-manager/agent/common.pb.go b/agent-manager/agent/common.pb.go index 0d4ccf71d..4407bccf4 100644 --- a/agent-manager/agent/common.pb.go +++ b/agent-manager/agent/common.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: common.proto package agent @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -116,23 +117,20 @@ func (ConnectorType) EnumDescriptor() ([]byte, []int) { } type ListRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + PageNumber int32 `protobuf:"varint,1,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + SearchQuery string `protobuf:"bytes,3,opt,name=search_query,json=searchQuery,proto3" json:"search_query,omitempty"` + SortBy string `protobuf:"bytes,4,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` unknownFields protoimpl.UnknownFields - - PageNumber int32 `protobuf:"varint,1,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` - PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - SearchQuery string `protobuf:"bytes,3,opt,name=search_query,json=searchQuery,proto3" json:"search_query,omitempty"` - SortBy string `protobuf:"bytes,4,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListRequest) Reset() { *x = ListRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListRequest) String() string { @@ -143,7 +141,7 @@ func (*ListRequest) ProtoMessage() {} func (x *ListRequest) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -187,21 +185,18 @@ func (x *ListRequest) GetSortBy() string { } type AuthResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` unknownFields protoimpl.UnknownFields - - Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + sizeCache protoimpl.SizeCache } func (x *AuthResponse) Reset() { *x = AuthResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AuthResponse) String() string { @@ -212,7 +207,7 @@ func (*AuthResponse) ProtoMessage() {} func (x *AuthResponse) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -242,20 +237,17 @@ func (x *AuthResponse) GetKey() string { } type DeleteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + DeletedBy string `protobuf:"bytes,1,opt,name=deleted_by,json=deletedBy,proto3" json:"deleted_by,omitempty"` unknownFields protoimpl.UnknownFields - - DeletedBy string `protobuf:"bytes,1,opt,name=deleted_by,json=deletedBy,proto3" json:"deleted_by,omitempty"` + sizeCache protoimpl.SizeCache } func (x *DeleteRequest) Reset() { *x = DeleteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DeleteRequest) String() string { @@ -266,7 +258,7 @@ func (*DeleteRequest) ProtoMessage() {} func (x *DeleteRequest) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -290,50 +282,45 @@ func (x *DeleteRequest) GetDeletedBy() string { var File_common_proto protoreflect.FileDescriptor -var file_common_proto_rawDesc = []byte{ - 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x87, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x75, - 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x65, - 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, - 0x69, 0x7a, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, - 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x22, - 0x30, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x22, 0x2e, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x42, - 0x79, 0x2a, 0x2e, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x4f, - 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x4f, 0x46, 0x46, 0x4c, 0x49, - 0x4e, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x02, 0x2a, 0x29, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, - 0x09, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x10, 0x01, 0x42, 0x32, 0x5a, 0x30, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_common_proto_rawDesc = "" + + "\n" + + "\fcommon.proto\x12\x05agent\"\x87\x01\n" + + "\vListRequest\x12\x1f\n" + + "\vpage_number\x18\x01 \x01(\x05R\n" + + "pageNumber\x12\x1b\n" + + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12!\n" + + "\fsearch_query\x18\x03 \x01(\tR\vsearchQuery\x12\x17\n" + + "\asort_by\x18\x04 \x01(\tR\x06sortBy\"0\n" + + "\fAuthResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\".\n" + + "\rDeleteRequest\x12\x1d\n" + + "\n" + + "deleted_by\x18\x01 \x01(\tR\tdeletedBy*.\n" + + "\x06Status\x12\n" + + "\n" + + "\x06ONLINE\x10\x00\x12\v\n" + + "\aOFFLINE\x10\x01\x12\v\n" + + "\aUNKNOWN\x10\x02*)\n" + + "\rConnectorType\x12\t\n" + + "\x05AGENT\x10\x00\x12\r\n" + + "\tCOLLECTOR\x10\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_common_proto_rawDescOnce sync.Once - file_common_proto_rawDescData = file_common_proto_rawDesc + file_common_proto_rawDescData []byte ) func file_common_proto_rawDescGZIP() []byte { file_common_proto_rawDescOnce.Do(func() { - file_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_proto_rawDescData) + file_common_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc))) }) return file_common_proto_rawDescData } var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_common_proto_goTypes = []interface{}{ +var file_common_proto_goTypes = []any{ (Status)(0), // 0: agent.Status (ConnectorType)(0), // 1: agent.ConnectorType (*ListRequest)(nil), // 2: agent.ListRequest @@ -353,49 +340,11 @@ func file_common_proto_init() { if File_common_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_common_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuthResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_common_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_common_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)), NumEnums: 2, NumMessages: 3, NumExtensions: 0, @@ -407,7 +356,6 @@ func file_common_proto_init() { MessageInfos: file_common_proto_msgTypes, }.Build() File_common_proto = out.File - file_common_proto_rawDesc = nil file_common_proto_goTypes = nil file_common_proto_depIdxs = nil } diff --git a/agent-manager/agent/ping.pb.go b/agent-manager/agent/ping.pb.go index 21ddaf763..b6f9787b9 100644 --- a/agent-manager/agent/ping.pb.go +++ b/agent-manager/agent/ping.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: ping.proto package agent @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -21,20 +22,17 @@ const ( ) type PingRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Type ConnectorType `protobuf:"varint,1,opt,name=type,proto3,enum=agent.ConnectorType" json:"type,omitempty"` unknownFields protoimpl.UnknownFields - - Type ConnectorType `protobuf:"varint,1,opt,name=type,proto3,enum=agent.ConnectorType" json:"type,omitempty"` + sizeCache protoimpl.SizeCache } func (x *PingRequest) Reset() { *x = PingRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_ping_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_ping_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PingRequest) String() string { @@ -45,7 +43,7 @@ func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { mi := &file_ping_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -68,20 +66,17 @@ func (x *PingRequest) GetType() ConnectorType { } type PingResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Received string `protobuf:"bytes,1,opt,name=received,proto3" json:"received,omitempty"` unknownFields protoimpl.UnknownFields - - Received string `protobuf:"bytes,1,opt,name=received,proto3" json:"received,omitempty"` + sizeCache protoimpl.SizeCache } func (x *PingResponse) Reset() { *x = PingResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_ping_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_ping_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PingResponse) String() string { @@ -92,7 +87,7 @@ func (*PingResponse) ProtoMessage() {} func (x *PingResponse) ProtoReflect() protoreflect.Message { mi := &file_ping_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -116,40 +111,31 @@ func (x *PingResponse) GetReceived() string { var File_ping_proto protoreflect.FileDescriptor -var file_ping_proto_rawDesc = []byte{ - 0x0a, 0x0a, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x1a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0x37, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2a, 0x0a, 0x0c, 0x50, 0x69, - 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x32, 0x42, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_ping_proto_rawDesc = "" + + "\n" + + "\n" + + "ping.proto\x12\x05agent\x1a\fcommon.proto\"7\n" + + "\vPingRequest\x12(\n" + + "\x04type\x18\x01 \x01(\x0e2\x14.agent.ConnectorTypeR\x04type\"*\n" + + "\fPingResponse\x12\x1a\n" + + "\breceived\x18\x01 \x01(\tR\breceived2B\n" + + "\vPingService\x123\n" + + "\x04Ping\x12\x12.agent.PingRequest\x1a\x13.agent.PingResponse\"\x00(\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_ping_proto_rawDescOnce sync.Once - file_ping_proto_rawDescData = file_ping_proto_rawDesc + file_ping_proto_rawDescData []byte ) func file_ping_proto_rawDescGZIP() []byte { file_ping_proto_rawDescOnce.Do(func() { - file_ping_proto_rawDescData = protoimpl.X.CompressGZIP(file_ping_proto_rawDescData) + file_ping_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_ping_proto_rawDesc), len(file_ping_proto_rawDesc))) }) return file_ping_proto_rawDescData } var file_ping_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_ping_proto_goTypes = []interface{}{ +var file_ping_proto_goTypes = []any{ (*PingRequest)(nil), // 0: agent.PingRequest (*PingResponse)(nil), // 1: agent.PingResponse (ConnectorType)(0), // 2: agent.ConnectorType @@ -171,37 +157,11 @@ func file_ping_proto_init() { return } file_common_proto_init() - if !protoimpl.UnsafeEnabled { - file_ping_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_ping_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_ping_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_ping_proto_rawDesc), len(file_ping_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, @@ -212,7 +172,6 @@ func file_ping_proto_init() { MessageInfos: file_ping_proto_msgTypes, }.Build() File_ping_proto = out.File - file_ping_proto_rawDesc = nil file_ping_proto_goTypes = nil file_ping_proto_depIdxs = nil } diff --git a/agent-manager/agent/ping_grpc.pb.go b/agent-manager/agent/ping_grpc.pb.go index f283dc80a..f3213500c 100644 --- a/agent-manager/agent/ping_grpc.pb.go +++ b/agent-manager/agent/ping_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 +// - protoc v5.29.3 // source: ping.proto package agent diff --git a/agent-manager/protos/agent.proto b/agent-manager/protos/agent.proto index 3f69e9a2b..c1db23819 100644 --- a/agent-manager/protos/agent.proto +++ b/agent-manager/protos/agent.proto @@ -79,6 +79,7 @@ message UtmCommand { string origin_type = 5; string origin_id = 6; string reason = 7; + string shell = 8; // Shell to execute command: "cmd", "powershell" (Windows), "sh", "bash" (Linux/macOS). Empty = default } message CommandResult { diff --git a/agent/agent/agent.pb.go b/agent/agent/agent.pb.go index 7c73da984..d5b52edb9 100644 --- a/agent/agent/agent.pb.go +++ b/agent/agent/agent.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: agent.proto package agent @@ -12,6 +12,7 @@ import ( timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -77,30 +78,27 @@ func (AgentCommandStatus) EnumDescriptor() ([]byte, []int) { } type AgentRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` - Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` - Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` - Platform string `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"` - Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` - RegisterBy string `protobuf:"bytes,6,opt,name=register_by,json=registerBy,proto3" json:"register_by,omitempty"` - Mac string `protobuf:"bytes,7,opt,name=mac,proto3" json:"mac,omitempty"` - OsMajorVersion string `protobuf:"bytes,8,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` - OsMinorVersion string `protobuf:"bytes,9,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` - Aliases string `protobuf:"bytes,10,opt,name=aliases,proto3" json:"aliases,omitempty"` - Addresses string `protobuf:"bytes,11,opt,name=addresses,proto3" json:"addresses,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` + Platform string `protobuf:"bytes,4,opt,name=platform,proto3" json:"platform,omitempty"` + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + RegisterBy string `protobuf:"bytes,6,opt,name=register_by,json=registerBy,proto3" json:"register_by,omitempty"` + Mac string `protobuf:"bytes,7,opt,name=mac,proto3" json:"mac,omitempty"` + OsMajorVersion string `protobuf:"bytes,8,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` + OsMinorVersion string `protobuf:"bytes,9,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` + Aliases string `protobuf:"bytes,10,opt,name=aliases,proto3" json:"aliases,omitempty"` + Addresses string `protobuf:"bytes,11,opt,name=addresses,proto3" json:"addresses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentRequest) Reset() { *x = AgentRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentRequest) String() string { @@ -111,7 +109,7 @@ func (*AgentRequest) ProtoMessage() {} func (x *AgentRequest) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -204,21 +202,18 @@ func (x *AgentRequest) GetAddresses() string { } type ListAgentsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Rows []*Agent `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` unknownFields protoimpl.UnknownFields - - Rows []*Agent `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` - Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListAgentsResponse) Reset() { *x = ListAgentsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListAgentsResponse) String() string { @@ -229,7 +224,7 @@ func (*ListAgentsResponse) ProtoMessage() {} func (x *ListAgentsResponse) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -259,33 +254,30 @@ func (x *ListAgentsResponse) GetTotal() int32 { } type Agent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` - Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` - Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` - Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=agent.Status" json:"status,omitempty"` - Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` - Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` - AgentKey string `protobuf:"bytes,7,opt,name=agent_key,json=agentKey,proto3" json:"agent_key,omitempty"` - Id uint32 `protobuf:"varint,8,opt,name=id,proto3" json:"id,omitempty"` - LastSeen string `protobuf:"bytes,9,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` - Mac string `protobuf:"bytes,10,opt,name=mac,proto3" json:"mac,omitempty"` - OsMajorVersion string `protobuf:"bytes,11,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` - OsMinorVersion string `protobuf:"bytes,12,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` - Aliases string `protobuf:"bytes,13,opt,name=aliases,proto3" json:"aliases,omitempty"` - Addresses string `protobuf:"bytes,14,opt,name=addresses,proto3" json:"addresses,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"` + Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` + Os string `protobuf:"bytes,3,opt,name=os,proto3" json:"os,omitempty"` + Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=agent.Status" json:"status,omitempty"` + Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"` + Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` + AgentKey string `protobuf:"bytes,7,opt,name=agent_key,json=agentKey,proto3" json:"agent_key,omitempty"` + Id uint32 `protobuf:"varint,8,opt,name=id,proto3" json:"id,omitempty"` + LastSeen string `protobuf:"bytes,9,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` + Mac string `protobuf:"bytes,10,opt,name=mac,proto3" json:"mac,omitempty"` + OsMajorVersion string `protobuf:"bytes,11,opt,name=os_major_version,json=osMajorVersion,proto3" json:"os_major_version,omitempty"` + OsMinorVersion string `protobuf:"bytes,12,opt,name=os_minor_version,json=osMinorVersion,proto3" json:"os_minor_version,omitempty"` + Aliases string `protobuf:"bytes,13,opt,name=aliases,proto3" json:"aliases,omitempty"` + Addresses string `protobuf:"bytes,14,opt,name=addresses,proto3" json:"addresses,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Agent) Reset() { *x = Agent{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Agent) String() string { @@ -296,7 +288,7 @@ func (*Agent) ProtoMessage() {} func (x *Agent) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -410,24 +402,21 @@ func (x *Agent) GetAddresses() string { } type BidirectionalStream struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to StreamMessage: + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to StreamMessage: // // *BidirectionalStream_Command // *BidirectionalStream_Result StreamMessage isBidirectionalStream_StreamMessage `protobuf_oneof:"stream_message"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *BidirectionalStream) Reset() { *x = BidirectionalStream{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BidirectionalStream) String() string { @@ -438,7 +427,7 @@ func (*BidirectionalStream) ProtoMessage() {} func (x *BidirectionalStream) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -453,23 +442,27 @@ func (*BidirectionalStream) Descriptor() ([]byte, []int) { return file_agent_proto_rawDescGZIP(), []int{3} } -func (m *BidirectionalStream) GetStreamMessage() isBidirectionalStream_StreamMessage { - if m != nil { - return m.StreamMessage +func (x *BidirectionalStream) GetStreamMessage() isBidirectionalStream_StreamMessage { + if x != nil { + return x.StreamMessage } return nil } func (x *BidirectionalStream) GetCommand() *UtmCommand { - if x, ok := x.GetStreamMessage().(*BidirectionalStream_Command); ok { - return x.Command + if x != nil { + if x, ok := x.StreamMessage.(*BidirectionalStream_Command); ok { + return x.Command + } } return nil } func (x *BidirectionalStream) GetResult() *CommandResult { - if x, ok := x.GetStreamMessage().(*BidirectionalStream_Result); ok { - return x.Result + if x != nil { + if x, ok := x.StreamMessage.(*BidirectionalStream_Result); ok { + return x.Result + } } return nil } @@ -491,26 +484,24 @@ func (*BidirectionalStream_Command) isBidirectionalStream_StreamMessage() {} func (*BidirectionalStream_Result) isBidirectionalStream_StreamMessage() {} type UtmCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` + ExecutedBy string `protobuf:"bytes,3,opt,name=executed_by,json=executedBy,proto3" json:"executed_by,omitempty"` + CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` + OriginType string `protobuf:"bytes,5,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` + OriginId string `protobuf:"bytes,6,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` + Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` + Shell string `protobuf:"bytes,8,opt,name=shell,proto3" json:"shell,omitempty"` // Shell to execute command: "cmd", "powershell" (Windows), "sh", "bash" (Linux/macOS). Empty = default unknownFields protoimpl.UnknownFields - - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Command string `protobuf:"bytes,2,opt,name=command,proto3" json:"command,omitempty"` - ExecutedBy string `protobuf:"bytes,3,opt,name=executed_by,json=executedBy,proto3" json:"executed_by,omitempty"` - CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` - OriginType string `protobuf:"bytes,5,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` - OriginId string `protobuf:"bytes,6,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` - Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` + sizeCache protoimpl.SizeCache } func (x *UtmCommand) Reset() { *x = UtmCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UtmCommand) String() string { @@ -521,7 +512,7 @@ func (*UtmCommand) ProtoMessage() {} func (x *UtmCommand) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -585,24 +576,28 @@ func (x *UtmCommand) GetReason() string { return "" } +func (x *UtmCommand) GetShell() string { + if x != nil { + return x.Shell + } + return "" +} + type CommandResult struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` + ExecutedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=executed_at,json=executedAt,proto3" json:"executed_at,omitempty"` + CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` unknownFields protoimpl.UnknownFields - - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Result string `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty"` - ExecutedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=executed_at,json=executedAt,proto3" json:"executed_at,omitempty"` - CmdId string `protobuf:"bytes,4,opt,name=cmd_id,json=cmdId,proto3" json:"cmd_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *CommandResult) Reset() { *x = CommandResult{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CommandResult) String() string { @@ -613,7 +608,7 @@ func (*CommandResult) ProtoMessage() {} func (x *CommandResult) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -657,21 +652,18 @@ func (x *CommandResult) GetCmdId() string { } type ListAgentsCommandsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Rows []*AgentCommand `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` unknownFields protoimpl.UnknownFields - - Rows []*AgentCommand `protobuf:"bytes,1,rep,name=rows,proto3" json:"rows,omitempty"` - Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListAgentsCommandsResponse) Reset() { *x = ListAgentsCommandsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListAgentsCommandsResponse) String() string { @@ -682,7 +674,7 @@ func (*ListAgentsCommandsResponse) ProtoMessage() {} func (x *ListAgentsCommandsResponse) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -712,10 +704,7 @@ func (x *ListAgentsCommandsResponse) GetTotal() int32 { } type AgentCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` CreatedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` AgentId uint32 `protobuf:"varint,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` @@ -727,15 +716,15 @@ type AgentCommand struct { Reason string `protobuf:"bytes,9,opt,name=reason,proto3" json:"reason,omitempty"` OriginType string `protobuf:"bytes,10,opt,name=origin_type,json=originType,proto3" json:"origin_type,omitempty"` OriginId string `protobuf:"bytes,11,opt,name=origin_id,json=originId,proto3" json:"origin_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AgentCommand) Reset() { *x = AgentCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_agent_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_agent_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AgentCommand) String() string { @@ -746,7 +735,7 @@ func (*AgentCommand) ProtoMessage() {} func (x *AgentCommand) ProtoReflect() protoreflect.Message { mi := &file_agent_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -840,182 +829,116 @@ func (x *AgentCommand) GetOriginId() string { var File_agent_proto protoreflect.FileDescriptor -var file_agent_proto_rawDesc = []byte{ - 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xbf, 0x02, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x5f, 0x62, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x42, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x63, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, - 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, - 0x73, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, - 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x4c, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, - 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x04, 0x72, - 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x14, 0x0a, - 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x22, 0x88, 0x03, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1a, 0x0a, - 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x4b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, - 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, - 0x61, 0x63, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x73, 0x5f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, - 0x4d, 0x61, 0x6a, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, - 0x6f, 0x73, 0x5f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x73, 0x4d, 0x69, 0x6e, 0x6f, 0x72, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, - 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, - 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x86, - 0x01, 0x0a, 0x13, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, - 0x55, 0x74, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x2e, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x42, 0x10, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xcf, 0x01, 0x0a, 0x0a, 0x55, 0x74, 0x6d, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x15, 0x0a, 0x06, - 0x63, 0x6d, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, - 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x96, 0x01, 0x0a, 0x0d, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x3b, - 0x0a, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x63, - 0x6d, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, 0x64, - 0x49, 0x64, 0x22, 0x5b, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x27, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x52, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x22, - 0xa1, 0x03, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x40, 0x0a, 0x0e, 0x63, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0d, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, - 0x64, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x63, 0x6d, 0x64, 0x5f, 0x69, 0x64, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6d, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x5f, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, - 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x72, 0x69, 0x67, 0x69, - 0x6e, 0x49, 0x64, 0x2a, 0x57, 0x0a, 0x12, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x4f, 0x54, - 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x51, - 0x55, 0x45, 0x55, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, - 0x47, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x45, 0x44, 0x10, - 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x32, 0x9c, 0x03, 0x0a, - 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3b, 0x0a, - 0x0d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0b, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x3d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, - 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x4b, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, - 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x1a, 0x1a, 0x2e, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2e, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, - 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4c, 0x0a, - 0x11, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x73, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x4f, 0x0a, 0x0c, 0x50, - 0x61, 0x6e, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x50, - 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x11, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x74, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x1a, 0x14, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x32, 0x5a, 0x30, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_agent_proto_rawDesc = "" + + "\n" + + "\vagent.proto\x12\x05agent\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\fcommon.proto\"\xbf\x02\n" + + "\fAgentRequest\x12\x0e\n" + + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12\x0e\n" + + "\x02os\x18\x03 \x01(\tR\x02os\x12\x1a\n" + + "\bplatform\x18\x04 \x01(\tR\bplatform\x12\x18\n" + + "\aversion\x18\x05 \x01(\tR\aversion\x12\x1f\n" + + "\vregister_by\x18\x06 \x01(\tR\n" + + "registerBy\x12\x10\n" + + "\x03mac\x18\a \x01(\tR\x03mac\x12(\n" + + "\x10os_major_version\x18\b \x01(\tR\x0eosMajorVersion\x12(\n" + + "\x10os_minor_version\x18\t \x01(\tR\x0eosMinorVersion\x12\x18\n" + + "\aaliases\x18\n" + + " \x01(\tR\aaliases\x12\x1c\n" + + "\taddresses\x18\v \x01(\tR\taddresses\"L\n" + + "\x12ListAgentsResponse\x12 \n" + + "\x04rows\x18\x01 \x03(\v2\f.agent.AgentR\x04rows\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"\x88\x03\n" + + "\x05Agent\x12\x0e\n" + + "\x02ip\x18\x01 \x01(\tR\x02ip\x12\x1a\n" + + "\bhostname\x18\x02 \x01(\tR\bhostname\x12\x0e\n" + + "\x02os\x18\x03 \x01(\tR\x02os\x12%\n" + + "\x06status\x18\x04 \x01(\x0e2\r.agent.StatusR\x06status\x12\x1a\n" + + "\bplatform\x18\x05 \x01(\tR\bplatform\x12\x18\n" + + "\aversion\x18\x06 \x01(\tR\aversion\x12\x1b\n" + + "\tagent_key\x18\a \x01(\tR\bagentKey\x12\x0e\n" + + "\x02id\x18\b \x01(\rR\x02id\x12\x1b\n" + + "\tlast_seen\x18\t \x01(\tR\blastSeen\x12\x10\n" + + "\x03mac\x18\n" + + " \x01(\tR\x03mac\x12(\n" + + "\x10os_major_version\x18\v \x01(\tR\x0eosMajorVersion\x12(\n" + + "\x10os_minor_version\x18\f \x01(\tR\x0eosMinorVersion\x12\x18\n" + + "\aaliases\x18\r \x01(\tR\aaliases\x12\x1c\n" + + "\taddresses\x18\x0e \x01(\tR\taddresses\"\x86\x01\n" + + "\x13BidirectionalStream\x12-\n" + + "\acommand\x18\x01 \x01(\v2\x11.agent.UtmCommandH\x00R\acommand\x12.\n" + + "\x06result\x18\x02 \x01(\v2\x14.agent.CommandResultH\x00R\x06resultB\x10\n" + + "\x0estream_message\"\xe5\x01\n" + + "\n" + + "UtmCommand\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x18\n" + + "\acommand\x18\x02 \x01(\tR\acommand\x12\x1f\n" + + "\vexecuted_by\x18\x03 \x01(\tR\n" + + "executedBy\x12\x15\n" + + "\x06cmd_id\x18\x04 \x01(\tR\x05cmdId\x12\x1f\n" + + "\vorigin_type\x18\x05 \x01(\tR\n" + + "originType\x12\x1b\n" + + "\torigin_id\x18\x06 \x01(\tR\boriginId\x12\x16\n" + + "\x06reason\x18\a \x01(\tR\x06reason\x12\x14\n" + + "\x05shell\x18\b \x01(\tR\x05shell\"\x96\x01\n" + + "\rCommandResult\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x16\n" + + "\x06result\x18\x02 \x01(\tR\x06result\x12;\n" + + "\vexecuted_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "executedAt\x12\x15\n" + + "\x06cmd_id\x18\x04 \x01(\tR\x05cmdId\"[\n" + + "\x1aListAgentsCommandsResponse\x12'\n" + + "\x04rows\x18\x01 \x03(\v2\x13.agent.AgentCommandR\x04rows\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"\xa1\x03\n" + + "\fAgentCommand\x129\n" + + "\n" + + "created_at\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "updated_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x19\n" + + "\bagent_id\x18\x03 \x01(\rR\aagentId\x12\x18\n" + + "\acommand\x18\x04 \x01(\tR\acommand\x12@\n" + + "\x0ecommand_status\x18\x05 \x01(\x0e2\x19.agent.AgentCommandStatusR\rcommandStatus\x12\x16\n" + + "\x06result\x18\x06 \x01(\tR\x06result\x12\x1f\n" + + "\vexecuted_by\x18\a \x01(\tR\n" + + "executedBy\x12\x15\n" + + "\x06cmd_id\x18\b \x01(\tR\x05cmdId\x12\x16\n" + + "\x06reason\x18\t \x01(\tR\x06reason\x12\x1f\n" + + "\vorigin_type\x18\n" + + " \x01(\tR\n" + + "originType\x12\x1b\n" + + "\torigin_id\x18\v \x01(\tR\boriginId*W\n" + + "\x12AgentCommandStatus\x12\x10\n" + + "\fNOT_EXECUTED\x10\x00\x12\t\n" + + "\x05QUEUE\x10\x01\x12\v\n" + + "\aPENDING\x10\x02\x12\f\n" + + "\bEXECUTED\x10\x03\x12\t\n" + + "\x05ERROR\x10\x042\x9c\x03\n" + + "\fAgentService\x12;\n" + + "\rRegisterAgent\x12\x13.agent.AgentRequest\x1a\x13.agent.AuthResponse\"\x00\x129\n" + + "\vUpdateAgent\x12\x13.agent.AgentRequest\x1a\x13.agent.AuthResponse\"\x00\x12:\n" + + "\vDeleteAgent\x12\x14.agent.DeleteRequest\x1a\x13.agent.AuthResponse\"\x00\x12=\n" + + "\n" + + "ListAgents\x12\x12.agent.ListRequest\x1a\x19.agent.ListAgentsResponse\"\x00\x12K\n" + + "\vAgentStream\x12\x1a.agent.BidirectionalStream\x1a\x1a.agent.BidirectionalStream\"\x00(\x010\x01\x12L\n" + + "\x11ListAgentCommands\x12\x12.agent.ListRequest\x1a!.agent.ListAgentsCommandsResponse\"\x002O\n" + + "\fPanelService\x12?\n" + + "\x0eProcessCommand\x12\x11.agent.UtmCommand\x1a\x14.agent.CommandResult\"\x00(\x010\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_agent_proto_rawDescOnce sync.Once - file_agent_proto_rawDescData = file_agent_proto_rawDesc + file_agent_proto_rawDescData []byte ) func file_agent_proto_rawDescGZIP() []byte { file_agent_proto_rawDescOnce.Do(func() { - file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_proto_rawDescData) + file_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc))) }) return file_agent_proto_rawDescData } var file_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 8) -var file_agent_proto_goTypes = []interface{}{ +var file_agent_proto_goTypes = []any{ (AgentCommandStatus)(0), // 0: agent.AgentCommandStatus (*AgentRequest)(nil), // 1: agent.AgentRequest (*ListAgentsResponse)(nil), // 2: agent.ListAgentsResponse @@ -1068,105 +991,7 @@ func file_agent_proto_init() { return } file_common_proto_init() - if !protoimpl.UnsafeEnabled { - file_agent_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAgentsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Agent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BidirectionalStream); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UtmCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CommandResult); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAgentsCommandsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_agent_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AgentCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_agent_proto_msgTypes[3].OneofWrappers = []interface{}{ + file_agent_proto_msgTypes[3].OneofWrappers = []any{ (*BidirectionalStream_Command)(nil), (*BidirectionalStream_Result)(nil), } @@ -1174,7 +999,7 @@ func file_agent_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_agent_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_proto_rawDesc), len(file_agent_proto_rawDesc)), NumEnums: 1, NumMessages: 8, NumExtensions: 0, @@ -1186,7 +1011,6 @@ func file_agent_proto_init() { MessageInfos: file_agent_proto_msgTypes, }.Build() File_agent_proto = out.File - file_agent_proto_rawDesc = nil file_agent_proto_goTypes = nil file_agent_proto_depIdxs = nil } diff --git a/agent/agent/agent_grpc.pb.go b/agent/agent/agent_grpc.pb.go index f75ba57b2..400673e17 100644 --- a/agent/agent/agent_grpc.pb.go +++ b/agent/agent/agent_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 +// - protoc v5.29.3 // source: agent.proto package agent diff --git a/agent/agent/common.pb.go b/agent/agent/common.pb.go index 0d4ccf71d..4407bccf4 100644 --- a/agent/agent/common.pb.go +++ b/agent/agent/common.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: common.proto package agent @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -116,23 +117,20 @@ func (ConnectorType) EnumDescriptor() ([]byte, []int) { } type ListRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + PageNumber int32 `protobuf:"varint,1,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + SearchQuery string `protobuf:"bytes,3,opt,name=search_query,json=searchQuery,proto3" json:"search_query,omitempty"` + SortBy string `protobuf:"bytes,4,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` unknownFields protoimpl.UnknownFields - - PageNumber int32 `protobuf:"varint,1,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` - PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - SearchQuery string `protobuf:"bytes,3,opt,name=search_query,json=searchQuery,proto3" json:"search_query,omitempty"` - SortBy string `protobuf:"bytes,4,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListRequest) Reset() { *x = ListRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListRequest) String() string { @@ -143,7 +141,7 @@ func (*ListRequest) ProtoMessage() {} func (x *ListRequest) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -187,21 +185,18 @@ func (x *ListRequest) GetSortBy() string { } type AuthResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` unknownFields protoimpl.UnknownFields - - Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + sizeCache protoimpl.SizeCache } func (x *AuthResponse) Reset() { *x = AuthResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AuthResponse) String() string { @@ -212,7 +207,7 @@ func (*AuthResponse) ProtoMessage() {} func (x *AuthResponse) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -242,20 +237,17 @@ func (x *AuthResponse) GetKey() string { } type DeleteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + DeletedBy string `protobuf:"bytes,1,opt,name=deleted_by,json=deletedBy,proto3" json:"deleted_by,omitempty"` unknownFields protoimpl.UnknownFields - - DeletedBy string `protobuf:"bytes,1,opt,name=deleted_by,json=deletedBy,proto3" json:"deleted_by,omitempty"` + sizeCache protoimpl.SizeCache } func (x *DeleteRequest) Reset() { *x = DeleteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_common_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_common_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DeleteRequest) String() string { @@ -266,7 +258,7 @@ func (*DeleteRequest) ProtoMessage() {} func (x *DeleteRequest) ProtoReflect() protoreflect.Message { mi := &file_common_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -290,50 +282,45 @@ func (x *DeleteRequest) GetDeletedBy() string { var File_common_proto protoreflect.FileDescriptor -var file_common_proto_rawDesc = []byte{ - 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x87, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x75, - 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x65, - 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, - 0x69, 0x7a, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x71, 0x75, - 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, - 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, - 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x72, 0x74, 0x42, 0x79, 0x22, - 0x30, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x22, 0x2e, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x42, - 0x79, 0x2a, 0x2e, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, 0x06, 0x4f, - 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x4f, 0x46, 0x46, 0x4c, 0x49, - 0x4e, 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x02, 0x2a, 0x29, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x47, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, - 0x09, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x10, 0x01, 0x42, 0x32, 0x5a, 0x30, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, - 0x61, 0x63, 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_common_proto_rawDesc = "" + + "\n" + + "\fcommon.proto\x12\x05agent\"\x87\x01\n" + + "\vListRequest\x12\x1f\n" + + "\vpage_number\x18\x01 \x01(\x05R\n" + + "pageNumber\x12\x1b\n" + + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12!\n" + + "\fsearch_query\x18\x03 \x01(\tR\vsearchQuery\x12\x17\n" + + "\asort_by\x18\x04 \x01(\tR\x06sortBy\"0\n" + + "\fAuthResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\".\n" + + "\rDeleteRequest\x12\x1d\n" + + "\n" + + "deleted_by\x18\x01 \x01(\tR\tdeletedBy*.\n" + + "\x06Status\x12\n" + + "\n" + + "\x06ONLINE\x10\x00\x12\v\n" + + "\aOFFLINE\x10\x01\x12\v\n" + + "\aUNKNOWN\x10\x02*)\n" + + "\rConnectorType\x12\t\n" + + "\x05AGENT\x10\x00\x12\r\n" + + "\tCOLLECTOR\x10\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_common_proto_rawDescOnce sync.Once - file_common_proto_rawDescData = file_common_proto_rawDesc + file_common_proto_rawDescData []byte ) func file_common_proto_rawDescGZIP() []byte { file_common_proto_rawDescOnce.Do(func() { - file_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_proto_rawDescData) + file_common_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc))) }) return file_common_proto_rawDescData } var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_common_proto_goTypes = []interface{}{ +var file_common_proto_goTypes = []any{ (Status)(0), // 0: agent.Status (ConnectorType)(0), // 1: agent.ConnectorType (*ListRequest)(nil), // 2: agent.ListRequest @@ -353,49 +340,11 @@ func file_common_proto_init() { if File_common_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_common_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuthResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_common_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_common_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)), NumEnums: 2, NumMessages: 3, NumExtensions: 0, @@ -407,7 +356,6 @@ func file_common_proto_init() { MessageInfos: file_common_proto_msgTypes, }.Build() File_common_proto = out.File - file_common_proto_rawDesc = nil file_common_proto_goTypes = nil file_common_proto_depIdxs = nil } diff --git a/agent/agent/incident_response.go b/agent/agent/incident_response.go index 3c9927d45..a7b87eec2 100644 --- a/agent/agent/incident_response.go +++ b/agent/agent/incident_response.go @@ -48,7 +48,7 @@ func IncidentResponseStream(cnf *config.Config, ctx context.Context) { switch msg := in.StreamMessage.(type) { case *BidirectionalStream_Command: - err = commandProcessor(path, stream, cnf, []string{msg.Command.Command, in.GetCommand().CmdId}) + err = commandProcessor(path, stream, cnf, msg.Command.Command, msg.Command.CmdId, msg.Command.Shell) if err != nil { action := HandleGRPCStreamError(err, "error sending result to server", &streamErrLogged) if action == ActionReconnect { @@ -62,31 +62,41 @@ func IncidentResponseStream(cnf *config.Config, ctx context.Context) { } } -func commandProcessor(path string, stream AgentService_AgentStreamClient, cnf *config.Config, commandPair []string) error { +func commandProcessor(path string, stream AgentService_AgentStreamClient, cnf *config.Config, command, cmdId, shell string) error { var result string var errB bool - utils.Logger.LogF(100, "Received command: %s", commandPair[0]) + utils.Logger.LogF(100, "Received command: %s (shell: %s)", command, shell) switch runtime.GOOS { case "windows": - result, errB = utils.ExecuteWithResult("cmd.exe", path, "/C", commandPair[0]) + if shell == "powershell" { + result, errB = utils.ExecuteWithResult("powershell.exe", path, "-Command", command) + } else { + // Default to cmd.exe (also handles shell == "" or shell == "cmd") + result, errB = utils.ExecuteWithResult("cmd.exe", path, "/C", command) + } case "linux", "darwin": - result, errB = utils.ExecuteWithResult("sh", path, "-c", commandPair[0]) + if shell == "bash" { + result, errB = utils.ExecuteWithResult("bash", path, "-c", command) + } else { + // Default to sh (also handles shell == "" or shell == "sh") + result, errB = utils.ExecuteWithResult("sh", path, "-c", command) + } default: utils.Logger.ErrorF("unsupported operating system: %s", runtime.GOOS) return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) } if errB { - utils.Logger.ErrorF("error executing command %s: %s", commandPair[0], result) + utils.Logger.ErrorF("error executing command %s: %s", command, result) } else { - utils.Logger.LogF(100, "Result when executing the command %s: %s", commandPair[0], result) + utils.Logger.LogF(100, "Result when executing the command %s: %s", command, result) } if err := stream.Send(&BidirectionalStream{ StreamMessage: &BidirectionalStream_Result{ - Result: &CommandResult{Result: result, AgentId: strconv.Itoa(int(cnf.AgentID)), ExecutedAt: timestamppb.Now(), CmdId: commandPair[1]}, + Result: &CommandResult{Result: result, AgentId: strconv.Itoa(int(cnf.AgentID)), ExecutedAt: timestamppb.Now(), CmdId: cmdId}, }, }); err != nil { return err diff --git a/agent/agent/ping.pb.go b/agent/agent/ping.pb.go index 21ddaf763..b6f9787b9 100644 --- a/agent/agent/ping.pb.go +++ b/agent/agent/ping.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.12 +// protoc-gen-go v1.36.10 +// protoc v5.29.3 // source: ping.proto package agent @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -21,20 +22,17 @@ const ( ) type PingRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Type ConnectorType `protobuf:"varint,1,opt,name=type,proto3,enum=agent.ConnectorType" json:"type,omitempty"` unknownFields protoimpl.UnknownFields - - Type ConnectorType `protobuf:"varint,1,opt,name=type,proto3,enum=agent.ConnectorType" json:"type,omitempty"` + sizeCache protoimpl.SizeCache } func (x *PingRequest) Reset() { *x = PingRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_ping_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_ping_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PingRequest) String() string { @@ -45,7 +43,7 @@ func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { mi := &file_ping_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -68,20 +66,17 @@ func (x *PingRequest) GetType() ConnectorType { } type PingResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Received string `protobuf:"bytes,1,opt,name=received,proto3" json:"received,omitempty"` unknownFields protoimpl.UnknownFields - - Received string `protobuf:"bytes,1,opt,name=received,proto3" json:"received,omitempty"` + sizeCache protoimpl.SizeCache } func (x *PingResponse) Reset() { *x = PingResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_ping_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_ping_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PingResponse) String() string { @@ -92,7 +87,7 @@ func (*PingResponse) ProtoMessage() {} func (x *PingResponse) ProtoReflect() protoreflect.Message { mi := &file_ping_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -116,40 +111,31 @@ func (x *PingResponse) GetReceived() string { var File_ping_proto protoreflect.FileDescriptor -var file_ping_proto_rawDesc = []byte{ - 0x0a, 0x0a, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x1a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x22, 0x37, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, - 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2a, 0x0a, 0x0c, 0x50, 0x69, - 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x32, 0x42, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x2e, - 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x75, 0x74, 0x6d, 0x73, 0x74, 0x61, 0x63, - 0x6b, 0x2f, 0x55, 0x54, 0x4d, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, - 0x2d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_ping_proto_rawDesc = "" + + "\n" + + "\n" + + "ping.proto\x12\x05agent\x1a\fcommon.proto\"7\n" + + "\vPingRequest\x12(\n" + + "\x04type\x18\x01 \x01(\x0e2\x14.agent.ConnectorTypeR\x04type\"*\n" + + "\fPingResponse\x12\x1a\n" + + "\breceived\x18\x01 \x01(\tR\breceived2B\n" + + "\vPingService\x123\n" + + "\x04Ping\x12\x12.agent.PingRequest\x1a\x13.agent.PingResponse\"\x00(\x01B2Z0github.com/utmstack/UTMStack/agent-manager/agentb\x06proto3" var ( file_ping_proto_rawDescOnce sync.Once - file_ping_proto_rawDescData = file_ping_proto_rawDesc + file_ping_proto_rawDescData []byte ) func file_ping_proto_rawDescGZIP() []byte { file_ping_proto_rawDescOnce.Do(func() { - file_ping_proto_rawDescData = protoimpl.X.CompressGZIP(file_ping_proto_rawDescData) + file_ping_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_ping_proto_rawDesc), len(file_ping_proto_rawDesc))) }) return file_ping_proto_rawDescData } var file_ping_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_ping_proto_goTypes = []interface{}{ +var file_ping_proto_goTypes = []any{ (*PingRequest)(nil), // 0: agent.PingRequest (*PingResponse)(nil), // 1: agent.PingResponse (ConnectorType)(0), // 2: agent.ConnectorType @@ -171,37 +157,11 @@ func file_ping_proto_init() { return } file_common_proto_init() - if !protoimpl.UnsafeEnabled { - file_ping_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_ping_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_ping_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_ping_proto_rawDesc), len(file_ping_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, @@ -212,7 +172,6 @@ func file_ping_proto_init() { MessageInfos: file_ping_proto_msgTypes, }.Build() File_ping_proto = out.File - file_ping_proto_rawDesc = nil file_ping_proto_goTypes = nil file_ping_proto_depIdxs = nil } diff --git a/agent/agent/ping_grpc.pb.go b/agent/agent/ping_grpc.pb.go index f283dc80a..f3213500c 100644 --- a/agent/agent/ping_grpc.pb.go +++ b/agent/agent/ping_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v3.21.12 +// - protoc v5.29.3 // source: ping.proto package agent diff --git a/agent/protos/agent.proto b/agent/protos/agent.proto index 5495bb3be..39d98fdd4 100644 --- a/agent/protos/agent.proto +++ b/agent/protos/agent.proto @@ -9,6 +9,7 @@ import "common.proto"; service AgentService { rpc RegisterAgent(AgentRequest) returns (AuthResponse) {} + rpc UpdateAgent(AgentRequest) returns (AuthResponse) {} rpc DeleteAgent(DeleteRequest) returns (AuthResponse) {} rpc ListAgents (ListRequest) returns (ListAgentsResponse) {} rpc AgentStream(stream BidirectionalStream) returns (stream BidirectionalStream) {} @@ -78,6 +79,7 @@ message UtmCommand { string origin_type = 5; string origin_id = 6; string reason = 7; + string shell = 8; // Shell to execute command: "cmd", "powershell" (Windows), "sh", "bash" (Linux/macOS). Empty = default } message CommandResult { diff --git a/agent/version.json b/agent/version.json index 685be4c14..b60b61712 100644 --- a/agent/version.json +++ b/agent/version.json @@ -1,4 +1,4 @@ { - "version": "11.1.3", - "updater_version": "1.0.2" + "version": "11.1.4", + "updater_version": "1.0.4" } From 5823657f2f1949eb12897f3463ff95ee31004ce6 Mon Sep 17 00:00:00 2001 From: Yorjander Hernandez Vergara Date: Wed, 18 Feb 2026 04:13:35 -0500 Subject: [PATCH 103/240] fix(agent): download version.json during install before agent registration --- agent/cmd/install.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/agent/cmd/install.go b/agent/cmd/install.go index ad9f9b076..1625015cc 100644 --- a/agent/cmd/install.go +++ b/agent/cmd/install.go @@ -10,6 +10,8 @@ import ( "github.com/utmstack/UTMStack/agent/config" "github.com/utmstack/UTMStack/agent/serv" "github.com/utmstack/UTMStack/agent/utils" + "github.com/utmstack/UTMStack/shared/fs" + "github.com/utmstack/UTMStack/shared/http" ) var installCmd = &cobra.Command{ @@ -34,6 +36,14 @@ var installCmd = &cobra.Command{ } fmt.Println("[OK]") + fmt.Print("Downloading version info ... ") + versionURL := fmt.Sprintf(config.DependUrl, cnf.Server, config.DependenciesPort, "version.json") + if err := http.DownloadFile(versionURL, nil, "version.json", fs.GetExecutablePath(), cnf.SkipCertValidation); err != nil { + fmt.Println("\nError downloading version.json: ", err) + os.Exit(1) + } + fmt.Println("[OK]") + fmt.Print("Configuring agent ... ") if err := pb.RegisterAgent(cnf, utmKey); err != nil { fmt.Println("\nError registering agent: ", err) From 069c6ae6577d2e085176f08eb2a02b8f6c2e9e12 Mon Sep 17 00:00:00 2001 From: Yorjander Hernandez Vergara Date: Wed, 18 Feb 2026 04:49:45 -0500 Subject: [PATCH 104/240] fix(updater,frontend): add legacy binary migration and fix agent search filter --- agent/updater/updates/update.go | 22 +++++++++++++++++++ .../agent-sidebar/agent-sidebar.component.ts | 5 ++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/agent/updater/updates/update.go b/agent/updater/updates/update.go index 0ac08ef37..fecaad400 100644 --- a/agent/updater/updates/update.go +++ b/agent/updater/updates/update.go @@ -24,6 +24,15 @@ type Version struct { Version string `json:"version"` } +// legacyServiceFile returns the old naming convention for the agent binary. +// This is used for migration from old agents that don't have OS/arch suffix. +func legacyServiceFile() string { + if runtime.GOOS == "windows" { + return "utmstack_agent_service.exe" + } + return "utmstack_agent_service" +} + var currentVersion = Version{} func UpdateDependencies(cnf *config.Config) { @@ -99,6 +108,19 @@ func runUpdateProcess(basePath string) error { time.Sleep(10 * time.Second) + // Migration: check if old naming convention exists and migrate to new naming + oldBinPath := filepath.Join(basePath, oldBin) + if !fs.Exists(oldBinPath) { + legacyBin := legacyServiceFile() + legacyBinPath := filepath.Join(basePath, legacyBin) + if fs.Exists(legacyBinPath) { + logger.Info("Migrating legacy binary from %s to %s", legacyBin, oldBin) + if err := os.Rename(legacyBinPath, oldBinPath); err != nil { + return fmt.Errorf("error migrating legacy binary: %v", err) + } + } + } + backupPath := filepath.Join(basePath, backupBin) if fs.Exists(backupPath) { logger.Info("Removing previous backup: %s", backupPath) diff --git a/frontend/src/app/incident-response/shared/component/agent-sidebar/agent-sidebar.component.ts b/frontend/src/app/incident-response/shared/component/agent-sidebar/agent-sidebar.component.ts index b1e39fd91..c3dac6220 100644 --- a/frontend/src/app/incident-response/shared/component/agent-sidebar/agent-sidebar.component.ts +++ b/frontend/src/app/incident-response/shared/component/agent-sidebar/agent-sidebar.component.ts @@ -24,10 +24,13 @@ export class AgentSidebarComponent implements OnInit { } searchAgent($event: string | number) { + const searchValue = $event.toString().trim(); + const searchQuery = searchValue ? `hostname.Contain=${searchValue}` : ''; + this.agentSidebarService.loadData({ ...this.request, page: 0, - searchQuery: $event.toString() + searchQuery }); } From dcbf36cc5bde86bf334a831b1833a8d63e317c3a Mon Sep 17 00:00:00 2001 From: Yorjander Hernandez Vergara Date: Wed, 18 Feb 2026 05:16:35 -0500 Subject: [PATCH 105/240] fix(agent): return errors from low-level packages instead of calling Fatal/Exit --- agent/agent/logprocessor.go | 63 +++++++++++++++++++++++++++++-------- agent/cmd/clean_logs.go | 6 +++- agent/database/db.go | 23 ++++++++------ agent/serv/service.go | 13 +++++--- 4 files changed, 78 insertions(+), 27 deletions(-) diff --git a/agent/agent/logprocessor.go b/agent/agent/logprocessor.go index fe59bf1aa..b67fff502 100644 --- a/agent/agent/logprocessor.go +++ b/agent/agent/logprocessor.go @@ -3,7 +3,6 @@ package agent import ( "context" "errors" - "os" "strconv" "strings" "sync" @@ -27,29 +26,47 @@ type LogProcessor struct { } var ( - processor LogProcessor - processorOnce sync.Once - LogQueue = make(chan *plugins.Log, 10000) - timeCLeanLogs = 10 * time.Minute + processor LogProcessor + processorOnce sync.Once + processorInitErr error + LogQueue = make(chan *plugins.Log, 10000) + timeCLeanLogs = 10 * time.Minute + + // ErrAgentUninstalled is returned when the agent uninstalls itself due to invalid key + ErrAgentUninstalled = errors.New("agent uninstalled due to invalid key") ) -func GetLogProcessor() LogProcessor { +func GetLogProcessor() (*LogProcessor, error) { processorOnce.Do(func() { + db, err := database.GetDB() + if err != nil { + processorInitErr = err + return + } processor = LogProcessor{ - db: database.GetDB(), + db: db, connErrWritten: false, ackErrWritten: false, sendErrWritten: false, } }) - return processor + if processorInitErr != nil { + return nil, processorInitErr + } + return &processor, nil } func (l *LogProcessor) ProcessLogs(cnf *config.Config, ctx context.Context) { go l.CleanCountedLogs() for { - ctxEof, cancelEof := context.WithCancel(context.Background()) + select { + case <-ctx.Done(): + utils.Logger.Info("ProcessLogs stopping due to context cancellation") + return + default: + } + connection, err := GetCorrelationConnection(cnf) if err != nil { if !l.connErrWritten { @@ -61,9 +78,23 @@ func (l *LogProcessor) ProcessLogs(cnf *config.Config, ctx context.Context) { } client := plugins.NewIntegrationClient(connection) - plClient := createClient(client, ctx) + plClient, err := createClient(client, ctx) + if err != nil { + if errors.Is(err, ErrAgentUninstalled) { + utils.Logger.Info("Agent uninstalled, stopping log processor") + return + } + if errors.Is(err, context.Canceled) { + utils.Logger.Info("ProcessLogs stopping due to context cancellation") + return + } + utils.Logger.ErrorF("error creating client: %v", err) + continue + } l.connErrWritten = false + // Create context only after successful client creation to avoid leaks + ctxEof, cancelEof := context.WithCancel(context.Background()) go l.handleAcknowledgements(plClient, ctxEof, cancelEof) l.processLogs(plClient, ctxEof, cancelEof) } @@ -173,13 +204,19 @@ func (l *LogProcessor) CleanCountedLogs() { } } -func createClient(client plugins.IntegrationClient, ctx context.Context) plugins.Integration_ProcessLogClient { +func createClient(client plugins.IntegrationClient, ctx context.Context) (plugins.Integration_ProcessLogClient, error) { var connErrMsgWritten bool invalidKeyCounter := 0 invalidKeyDelay := timeToSleep maxInvalidKeyDelay := 5 * time.Minute maxInvalidKeyAttempts := 100 // ~8+ hours with backoff before uninstall for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + plClient, err := client.ProcessLog(ctx) if err != nil { if strings.Contains(err.Error(), "invalid agent key") { @@ -188,7 +225,7 @@ func createClient(client plugins.IntegrationClient, ctx context.Context) plugins if invalidKeyCounter >= maxInvalidKeyAttempts { utils.Logger.ErrorF("uninstalling agent after %d consecutive invalid key errors", maxInvalidKeyAttempts) _ = UninstallAll() - os.Exit(1) + return nil, ErrAgentUninstalled } time.Sleep(invalidKeyDelay) invalidKeyDelay = utils.IncrementReconnectDelay(invalidKeyDelay, maxInvalidKeyDelay) @@ -204,7 +241,7 @@ func createClient(client plugins.IntegrationClient, ctx context.Context) plugins time.Sleep(timeToSleep) continue } - return plClient + return plClient, nil } } diff --git a/agent/cmd/clean_logs.go b/agent/cmd/clean_logs.go index fcd422c19..56ce119a9 100644 --- a/agent/cmd/clean_logs.go +++ b/agent/cmd/clean_logs.go @@ -18,7 +18,11 @@ var cleanLogsCmd = &cobra.Command{ PreRunE: requireInstalled, RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Cleaning old logs ...") - db := database.GetDB() + db, err := database.GetDB() + if err != nil { + fmt.Println("Error initializing database: ", err) + os.Exit(1) + } datR, err := agent.GetDataRetention() if err != nil { fmt.Println("Error getting retention: ", err) diff --git a/agent/database/db.go b/agent/database/db.go index 73ebca493..a9e2af5b6 100644 --- a/agent/database/db.go +++ b/agent/database/db.go @@ -3,7 +3,6 @@ package database import ( "errors" "fmt" - "log" "os" "path/filepath" "sync" @@ -18,6 +17,7 @@ import ( var ( dbInstance *Database dbOnce sync.Once + dbInitErr error ) type Database struct { @@ -112,18 +112,20 @@ func (d *Database) DeleteOld(data interface{}, retentionMegabytes int) (int, err return rowsAffected, nil } -func GetDB() *Database { +func GetDB() (*Database, error) { dbOnce.Do(func() { path := filepath.Join(fs.GetExecutablePath(), "logs_process") - err := fs.CreateDirIfNotExist(path) - if err != nil { - log.Fatalf("error creating database path: %v", err) + if err := fs.CreateDirIfNotExist(path); err != nil { + dbInitErr = fmt.Errorf("creating database path: %w", err) + return } + path = config.LogsDBFile if _, err := os.Stat(path); os.IsNotExist(err) { file, err := os.Create(path) if err != nil { - log.Fatalf("error creating database file: %v", err) + dbInitErr = fmt.Errorf("creating database file: %w", err) + return } file.Close() } @@ -132,14 +134,17 @@ func GetDB() *Database { Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { - log.Fatalf("error connecting with database: %v", err) + dbInitErr = fmt.Errorf("connecting with database: %w", err) + return } dbInstance = &Database{db: conn} - }) - return dbInstance + if dbInitErr != nil { + return nil, dbInitErr + } + return dbInstance, nil } func GetDatabaseSizeInMB() (int, error) { diff --git a/agent/serv/service.go b/agent/serv/service.go index c90849e16..ced41abf9 100644 --- a/agent/serv/service.go +++ b/agent/serv/service.go @@ -63,7 +63,7 @@ func (p *program) Stop(_ service.Service) error { } // Close database - if db := database.GetDB(); db != nil { + if db, err := database.GetDB(); err == nil && db != nil { if err := db.Close(); err != nil { utils.Logger.ErrorF("error closing database: %v", err) } @@ -94,9 +94,10 @@ func (p *program) run() { utils.Logger.Fatal("error getting config: %v", err) } - db := database.GetDB() - err = db.Migrate(models.Log{}) + db, err := database.GetDB() if err != nil { + utils.Logger.ErrorF("error initializing database: %v", err) + } else if err = db.Migrate(models.Log{}); err != nil { utils.Logger.ErrorF("error migrating logs table: %v", err) } @@ -125,7 +126,11 @@ func (p *program) run() { }) p.goSafe("ProcessLogs", func() { - logProcessor := pb.GetLogProcessor() + logProcessor, err := pb.GetLogProcessor() + if err != nil { + utils.Logger.ErrorF("error initializing log processor: %v", err) + return + } logProcessor.ProcessLogs(cnf, ctx) }) From f11a76504125286945cc5cc96353e1af3ade6389 Mon Sep 17 00:00:00 2001 From: Yorjander Hernandez Vergara Date: Wed, 18 Feb 2026 05:22:40 -0500 Subject: [PATCH 106/240] refactor[agent](collector): use fsnotify for config changes instead of polling --- agent/collector/configwatcher/watcher.go | 95 ++++++++++++++++++++++++ agent/collector/file/file.go | 25 ++----- agent/collector/netflow/netflow.go | 18 ++--- agent/collector/syslog/syslog.go | 19 +---- agent/go.mod | 1 + agent/go.sum | 2 + 6 files changed, 115 insertions(+), 45 deletions(-) create mode 100644 agent/collector/configwatcher/watcher.go diff --git a/agent/collector/configwatcher/watcher.go b/agent/collector/configwatcher/watcher.go new file mode 100644 index 000000000..dc1149682 --- /dev/null +++ b/agent/collector/configwatcher/watcher.go @@ -0,0 +1,95 @@ +// Package configwatcher provides a shared config file watcher using fsnotify. +package configwatcher + +import ( + "context" + "path/filepath" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/utmstack/UTMStack/agent/config" + "github.com/utmstack/UTMStack/agent/utils" +) + +const ( + // FallbackInterval is used as a safety net in case fsnotify misses events + FallbackInterval = 5 * time.Minute +) + +// Watch monitors the collector config file for changes and calls onConfigChange +// when the file is modified. It also calls onConfigChange periodically as a fallback. +// This function blocks until ctx is cancelled. +func Watch(ctx context.Context, name string, onConfigChange func()) { + // Initial call + onConfigChange() + + // Set up fsnotify watcher + watcher, err := fsnotify.NewWatcher() + if err != nil { + utils.Logger.ErrorF("%s: failed to create fsnotify watcher: %v, falling back to polling", name, err) + runPollingFallback(ctx, name, onConfigChange) + return + } + defer watcher.Close() + + // Watch the directory containing the config file + configDir := filepath.Dir(config.CollectorFileName) + configBase := filepath.Base(config.CollectorFileName) + + if err := watcher.Add(configDir); err != nil { + utils.Logger.ErrorF("%s: failed to watch config directory: %v, falling back to polling", name, err) + runPollingFallback(ctx, name, onConfigChange) + return + } + + utils.Logger.Info("%s: watching config file for changes", name) + + // Fallback timer as safety net + fallbackTicker := time.NewTicker(FallbackInterval) + defer fallbackTicker.Stop() + + for { + select { + case <-ctx.Done(): + utils.Logger.Info("%s: stopping config watcher", name) + return + + case event, ok := <-watcher.Events: + if !ok { + return + } + if filepath.Base(event.Name) == configBase { + if event.Op&(fsnotify.Write|fsnotify.Create) != 0 { + utils.Logger.Info("%s: config file changed, reconciling", name) + onConfigChange() + } + } + + case err, ok := <-watcher.Errors: + if !ok { + return + } + utils.Logger.ErrorF("%s: fsnotify error: %v", name, err) + + case <-fallbackTicker.C: + onConfigChange() + } + } +} + +// runPollingFallback is used when fsnotify cannot be initialized. +func runPollingFallback(ctx context.Context, name string, onConfigChange func()) { + utils.Logger.Info("%s: using polling fallback", name) + ticker := time.NewTicker(FallbackInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + utils.Logger.Info("%s: stopping config watcher", name) + return + case <-ticker.C: + onConfigChange() + } + } +} diff --git a/agent/collector/file/file.go b/agent/collector/file/file.go index 4dccc1e2e..7083e3991 100644 --- a/agent/collector/file/file.go +++ b/agent/collector/file/file.go @@ -11,15 +11,13 @@ import ( "github.com/threatwinds/go-sdk/entities" "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/collector/configwatcher" "github.com/utmstack/UTMStack/agent/collector/schema" "github.com/utmstack/UTMStack/agent/config" "github.com/utmstack/UTMStack/agent/utils" ) -const ( - reconcileInterval = 10 * time.Second - pollInterval = 1 * time.Second -) +const pollInterval = 1 * time.Second // fileWatcher represents an active file being tailed. type fileWatcher struct { @@ -62,21 +60,14 @@ func (fc *FileCollector) Stop() { } } -// Start begins the reconciliation loop. It reads the collector config every -// 10 seconds and adjusts file watchers as needed. +// Start begins watching for configuration changes using fsnotify. +// It performs an initial reconciliation and then reacts to config file changes. func (fc *FileCollector) Start(ctx context.Context, queue chan *plugins.Log) { fc.queue = queue - for { - select { - case <-ctx.Done(): - utils.Logger.Info("file collector stopping due to context cancellation") - fc.Stop() - return - default: - fc.reconcile(ctx) - time.Sleep(reconcileInterval) - } - } + configwatcher.Watch(ctx, "file collector", func() { + fc.reconcile(ctx) + }) + fc.Stop() } func (fc *FileCollector) reconcile(ctx context.Context) { diff --git a/agent/collector/netflow/netflow.go b/agent/collector/netflow/netflow.go index d41386586..9e26e5628 100644 --- a/agent/collector/netflow/netflow.go +++ b/agent/collector/netflow/netflow.go @@ -17,15 +17,15 @@ import ( tehmaze "github.com/tehmaze/netflow" "github.com/tehmaze/netflow/session" "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/collector/configwatcher" "github.com/utmstack/UTMStack/agent/collector/schema" "github.com/utmstack/UTMStack/agent/config" "github.com/utmstack/UTMStack/agent/utils" ) const ( - reconcileInterval = 10 * time.Second cacheCleanupInterval = 5 * time.Minute - cacheTTL = 30 * time.Minute + cacheTTL = 30 * time.Minute ) // templateSystem implements netflow.NetFlowTemplateSystem for goflow2 @@ -122,7 +122,8 @@ func (nc *NetflowCollector) Stop() { nc.disablePort() } -// Start begins the reconciliation loop. +// Start begins watching for configuration changes using fsnotify. +// It performs an initial reconciliation and then reacts to config file changes. func (nc *NetflowCollector) Start(ctx context.Context, queue chan *plugins.Log) { nc.queue = queue @@ -147,16 +148,7 @@ func (nc *NetflowCollector) Start(ctx context.Context, queue chan *plugins.Log) } }() - for { - select { - case <-ctx.Done(): - utils.Logger.Info("netflow collector stopping due to context cancellation") - return - default: - nc.reconcile() - time.Sleep(reconcileInterval) - } - } + configwatcher.Watch(ctx, "netflow collector", nc.reconcile) } func (nc *NetflowCollector) reconcile() { diff --git a/agent/collector/syslog/syslog.go b/agent/collector/syslog/syslog.go index ba318febc..60435263f 100644 --- a/agent/collector/syslog/syslog.go +++ b/agent/collector/syslog/syslog.go @@ -6,13 +6,12 @@ import ( "time" "github.com/threatwinds/go-sdk/plugins" + "github.com/utmstack/UTMStack/agent/collector/configwatcher" "github.com/utmstack/UTMStack/agent/collector/schema" "github.com/utmstack/UTMStack/agent/config" "github.com/utmstack/UTMStack/agent/utils" ) -const reconcileInterval = 10 * time.Second - // SyslogCollector manages all syslog instances. It reads the config file // periodically and reconciles port state internally. type SyslogCollector struct { @@ -41,21 +40,11 @@ func (sc *SyslogCollector) Stop() { } } -// Start begins the reconciliation loop. It reads the collector config every -// 10 seconds and adjusts listening ports as needed. This is the only entry -// point for syslog port management — no external code touches ports. +// Start begins watching for configuration changes using fsnotify. +// It performs an initial reconciliation and then reacts to config file changes. func (sc *SyslogCollector) Start(ctx context.Context, queue chan *plugins.Log) { sc.queue = queue - for { - select { - case <-ctx.Done(): - utils.Logger.Info("syslog collector stopping due to context cancellation") - return - default: - sc.reconcile() - time.Sleep(reconcileInterval) - } - } + configwatcher.Watch(ctx, "syslog collector", sc.reconcile) } func (sc *SyslogCollector) reconcile() { diff --git a/agent/go.mod b/agent/go.mod index aec39b5d0..b90c5ee3e 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -31,6 +31,7 @@ require ( github.com/cloudwego/base64x v0.1.6 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/go-windows v1.0.2 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect diff --git a/agent/go.sum b/agent/go.sum index 9f727d900..cfb551e58 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -25,6 +25,8 @@ github.com/elastic/go-sysinfo v1.15.4 h1:A3zQcunCxik14MgXu39cXFXcIw2sFXZ0zL886ey github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZPk3G244lHrU= github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI= github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8= +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/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= From 83c6dfea8d850b8010bc8c8cc52634f38973516e Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 18 Feb 2026 08:47:06 -0600 Subject: [PATCH 107/240] feat(agent): add shell field to UtmCommand for enhanced command execution context --- .../service/grpc/AgentManagerGrpc.java | 65 ++++----- .../utmstack/service/grpc/UtmCommand.java | 136 ++++++++++++++++++ .../service/grpc/UtmCommandOrBuilder.java | 12 ++ backend/src/main/proto/agent.proto | 1 + 4 files changed, 182 insertions(+), 32 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/grpc/AgentManagerGrpc.java b/backend/src/main/java/com/park/utmstack/service/grpc/AgentManagerGrpc.java index d48b04bf1..b72898c05 100644 --- a/backend/src/main/java/com/park/utmstack/service/grpc/AgentManagerGrpc.java +++ b/backend/src/main/java/com/park/utmstack/service/grpc/AgentManagerGrpc.java @@ -92,39 +92,40 @@ public static void registerAllExtensions( "\n\taddresses\030\016 \001(\t\"u\n\023BidirectionalStream" + "\022$\n\007command\030\001 \001(\0132\021.agent.UtmCommandH\000\022&" + "\n\006result\030\002 \001(\0132\024.agent.CommandResultH\000B\020" + - "\n\016stream_message\"\214\001\n\nUtmCommand\022\020\n\010agent" + + "\n\016stream_message\"\233\001\n\nUtmCommand\022\020\n\010agent" + "_id\030\001 \001(\t\022\017\n\007command\030\002 \001(\t\022\023\n\013executed_b" + "y\030\003 \001(\t\022\016\n\006cmd_id\030\004 \001(\t\022\023\n\013origin_type\030\005" + - " \001(\t\022\021\n\torigin_id\030\006 \001(\t\022\016\n\006reason\030\007 \001(\t\"" + - "r\n\rCommandResult\022\020\n\010agent_id\030\001 \001(\t\022\016\n\006re" + - "sult\030\002 \001(\t\022/\n\013executed_at\030\003 \001(\0132\032.google" + - ".protobuf.Timestamp\022\016\n\006cmd_id\030\004 \001(\t\"N\n\032L" + - "istAgentsCommandsResponse\022!\n\004rows\030\001 \003(\0132" + - "\023.agent.AgentCommand\022\r\n\005total\030\002 \001(\005\"\261\002\n\014" + - "AgentCommand\022.\n\ncreated_at\030\001 \001(\0132\032.googl" + - "e.protobuf.Timestamp\022.\n\nupdated_at\030\002 \001(\013" + - "2\032.google.protobuf.Timestamp\022\020\n\010agent_id" + - "\030\003 \001(\r\022\017\n\007command\030\004 \001(\t\0221\n\016command_statu" + - "s\030\005 \001(\0162\031.agent.AgentCommandStatus\022\016\n\006re" + - "sult\030\006 \001(\t\022\023\n\013executed_by\030\007 \001(\t\022\016\n\006cmd_i" + - "d\030\010 \001(\t\022\016\n\006reason\030\t \001(\t\022\023\n\013origin_type\030\n" + - " \001(\t\022\021\n\torigin_id\030\013 \001(\t*W\n\022AgentCommandS" + - "tatus\022\020\n\014NOT_EXECUTED\020\000\022\t\n\005QUEUE\020\001\022\013\n\007PE" + - "NDING\020\002\022\014\n\010EXECUTED\020\003\022\t\n\005ERROR\020\0042\234\003\n\014Age" + - "ntService\0229\n\013UpdateAgent\022\023.agent.AgentRe" + - "quest\032\023.agent.AuthResponse\"\000\022;\n\rRegister" + - "Agent\022\023.agent.AgentRequest\032\023.agent.AuthR" + - "esponse\"\000\022:\n\013DeleteAgent\022\024.agent.DeleteR" + - "equest\032\023.agent.AuthResponse\"\000\022=\n\nListAge" + - "nts\022\022.agent.ListRequest\032\031.agent.ListAgen" + - "tsResponse\"\000\022K\n\013AgentStream\022\032.agent.Bidi" + - "rectionalStream\032\032.agent.BidirectionalStr" + - "eam\"\000(\0010\001\022L\n\021ListAgentCommands\022\022.agent.L" + - "istRequest\032!.agent.ListAgentsCommandsRes" + - "ponse\"\0002O\n\014PanelService\022?\n\016ProcessComman" + - "d\022\021.agent.UtmCommand\032\024.agent.CommandResu" + - "lt\"\000(\0010\001B7\n\036com.park.utmstack.service.gr" + - "pcB\020AgentManagerGrpcP\001\210\001\001b\006proto3" + " \001(\t\022\021\n\torigin_id\030\006 \001(\t\022\016\n\006reason\030\007 \001(\t\022" + + "\r\n\005shell\030\010 \001(\t\"r\n\rCommandResult\022\020\n\010agent" + + "_id\030\001 \001(\t\022\016\n\006result\030\002 \001(\t\022/\n\013executed_at" + + "\030\003 \001(\0132\032.google.protobuf.Timestamp\022\016\n\006cm" + + "d_id\030\004 \001(\t\"N\n\032ListAgentsCommandsResponse" + + "\022!\n\004rows\030\001 \003(\0132\023.agent.AgentCommand\022\r\n\005t" + + "otal\030\002 \001(\005\"\261\002\n\014AgentCommand\022.\n\ncreated_a" + + "t\030\001 \001(\0132\032.google.protobuf.Timestamp\022.\n\nu" + + "pdated_at\030\002 \001(\0132\032.google.protobuf.Timest" + + "amp\022\020\n\010agent_id\030\003 \001(\r\022\017\n\007command\030\004 \001(\t\0221" + + "\n\016command_status\030\005 \001(\0162\031.agent.AgentComm" + + "andStatus\022\016\n\006result\030\006 \001(\t\022\023\n\013executed_by" + + "\030\007 \001(\t\022\016\n\006cmd_id\030\010 \001(\t\022\016\n\006reason\030\t \001(\t\022\023" + + "\n\013origin_type\030\n \001(\t\022\021\n\torigin_id\030\013 \001(\t*W" + + "\n\022AgentCommandStatus\022\020\n\014NOT_EXECUTED\020\000\022\t" + + "\n\005QUEUE\020\001\022\013\n\007PENDING\020\002\022\014\n\010EXECUTED\020\003\022\t\n\005" + + "ERROR\020\0042\234\003\n\014AgentService\0229\n\013UpdateAgent\022" + + "\023.agent.AgentRequest\032\023.agent.AuthRespons" + + "e\"\000\022;\n\rRegisterAgent\022\023.agent.AgentReques" + + "t\032\023.agent.AuthResponse\"\000\022:\n\013DeleteAgent\022" + + "\024.agent.DeleteRequest\032\023.agent.AuthRespon" + + "se\"\000\022=\n\nListAgents\022\022.agent.ListRequest\032\031" + + ".agent.ListAgentsResponse\"\000\022K\n\013AgentStre" + + "am\022\032.agent.BidirectionalStream\032\032.agent.B" + + "idirectionalStream\"\000(\0010\001\022L\n\021ListAgentCom" + + "mands\022\022.agent.ListRequest\032!.agent.ListAg" + + "entsCommandsResponse\"\0002O\n\014PanelService\022?" + + "\n\016ProcessCommand\022\021.agent.UtmCommand\032\024.ag" + + "ent.CommandResult\"\000(\0010\001B7\n\036com.park.utms" + + "tack.service.grpcB\020AgentManagerGrpcP\001\210\001\001" + + "b\006proto3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, @@ -161,7 +162,7 @@ public static void registerAllExtensions( internal_static_agent_UtmCommand_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_agent_UtmCommand_descriptor, - new java.lang.String[] { "AgentId", "Command", "ExecutedBy", "CmdId", "OriginType", "OriginId", "Reason", }); + new java.lang.String[] { "AgentId", "Command", "ExecutedBy", "CmdId", "OriginType", "OriginId", "Reason", "Shell", }); internal_static_agent_CommandResult_descriptor = getDescriptor().getMessageTypes().get(5); internal_static_agent_CommandResult_fieldAccessorTable = new diff --git a/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommand.java b/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommand.java index 83b39c5a5..84c4f287c 100644 --- a/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommand.java +++ b/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommand.java @@ -34,6 +34,7 @@ private UtmCommand() { originType_ = ""; originId_ = ""; reason_ = ""; + shell_ = ""; } public static final com.google.protobuf.Descriptors.Descriptor @@ -322,6 +323,45 @@ public java.lang.String getReason() { } } + public static final int SHELL_FIELD_NUMBER = 8; + @SuppressWarnings("serial") + private volatile java.lang.Object shell_ = ""; + /** + * string shell = 8; + * @return The shell. + */ + @java.lang.Override + public java.lang.String getShell() { + java.lang.Object ref = shell_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + shell_ = s; + return s; + } + } + /** + * string shell = 8; + * @return The bytes for shell. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getShellBytes() { + java.lang.Object ref = shell_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + shell_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { @@ -357,6 +397,9 @@ public void writeTo(com.google.protobuf.CodedOutputStream output) if (!com.google.protobuf.GeneratedMessage.isStringEmpty(reason_)) { com.google.protobuf.GeneratedMessage.writeString(output, 7, reason_); } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(shell_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 8, shell_); + } getUnknownFields().writeTo(output); } @@ -387,6 +430,9 @@ public int getSerializedSize() { if (!com.google.protobuf.GeneratedMessage.isStringEmpty(reason_)) { size += com.google.protobuf.GeneratedMessage.computeStringSize(7, reason_); } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(shell_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(8, shell_); + } size += getUnknownFields().getSerializedSize(); memoizedSize = size; return size; @@ -416,6 +462,8 @@ public boolean equals(final java.lang.Object obj) { .equals(other.getOriginId())) return false; if (!getReason() .equals(other.getReason())) return false; + if (!getShell() + .equals(other.getShell())) return false; if (!getUnknownFields().equals(other.getUnknownFields())) return false; return true; } @@ -441,6 +489,8 @@ public int hashCode() { hash = (53 * hash) + getOriginId().hashCode(); hash = (37 * hash) + REASON_FIELD_NUMBER; hash = (53 * hash) + getReason().hashCode(); + hash = (37 * hash) + SHELL_FIELD_NUMBER; + hash = (53 * hash) + getShell().hashCode(); hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -579,6 +629,7 @@ public Builder clear() { originType_ = ""; originId_ = ""; reason_ = ""; + shell_ = ""; return this; } @@ -633,6 +684,9 @@ private void buildPartial0(com.park.utmstack.service.grpc.UtmCommand result) { if (((from_bitField0_ & 0x00000040) != 0)) { result.reason_ = reason_; } + if (((from_bitField0_ & 0x00000080) != 0)) { + result.shell_ = shell_; + } } @java.lang.Override @@ -682,6 +736,11 @@ public Builder mergeFrom(com.park.utmstack.service.grpc.UtmCommand other) { bitField0_ |= 0x00000040; onChanged(); } + if (!other.getShell().isEmpty()) { + shell_ = other.shell_; + bitField0_ |= 0x00000080; + onChanged(); + } this.mergeUnknownFields(other.getUnknownFields()); onChanged(); return this; @@ -743,6 +802,11 @@ public Builder mergeFrom( bitField0_ |= 0x00000040; break; } // case 58 + case 66: { + shell_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000080; + break; + } // case 66 default: { if (!super.parseUnknownField(input, extensionRegistry, tag)) { done = true; // was an endgroup tag @@ -1264,6 +1328,78 @@ public Builder setReasonBytes( return this; } + private java.lang.Object shell_ = ""; + /** + * string shell = 8; + * @return The shell. + */ + public java.lang.String getShell() { + java.lang.Object ref = shell_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + shell_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string shell = 8; + * @return The bytes for shell. + */ + public com.google.protobuf.ByteString + getShellBytes() { + java.lang.Object ref = shell_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + shell_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string shell = 8; + * @param value The shell to set. + * @return This builder for chaining. + */ + public Builder setShell( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + shell_ = value; + bitField0_ |= 0x00000080; + onChanged(); + return this; + } + /** + * string shell = 8; + * @return This builder for chaining. + */ + public Builder clearShell() { + shell_ = getDefaultInstance().getShell(); + bitField0_ = (bitField0_ & ~0x00000080); + onChanged(); + return this; + } + /** + * string shell = 8; + * @param value The bytes for shell to set. + * @return This builder for chaining. + */ + public Builder setShellBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + shell_ = value; + bitField0_ |= 0x00000080; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:agent.UtmCommand) } diff --git a/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommandOrBuilder.java b/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommandOrBuilder.java index 7a7305596..215ccc7ad 100644 --- a/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommandOrBuilder.java +++ b/backend/src/main/java/com/park/utmstack/service/grpc/UtmCommandOrBuilder.java @@ -92,4 +92,16 @@ public interface UtmCommandOrBuilder extends */ com.google.protobuf.ByteString getReasonBytes(); + + /** + * string shell = 8; + * @return The shell. + */ + java.lang.String getShell(); + /** + * string shell = 8; + * @return The bytes for shell. + */ + com.google.protobuf.ByteString + getShellBytes(); } diff --git a/backend/src/main/proto/agent.proto b/backend/src/main/proto/agent.proto index c9dc990ef..d5ee2df98 100644 --- a/backend/src/main/proto/agent.proto +++ b/backend/src/main/proto/agent.proto @@ -82,6 +82,7 @@ message UtmCommand { string origin_type = 5; string origin_id = 6; string reason = 7; + string shell = 8; } message CommandResult { From 3f3de424d852a30d7961644dd1ab0683adfa3782 Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Wed, 18 Feb 2026 17:58:03 +0300 Subject: [PATCH 108/240] feat(fortinet): update fortinet filter --- filters/fortinet/fortinet.yml | 922 +++++++++++++++++++++++++++++++++- 1 file changed, 921 insertions(+), 1 deletion(-) diff --git a/filters/fortinet/fortinet.yml b/filters/fortinet/fortinet.yml index b973d3c23..f2cd81998 100644 --- a/filters/fortinet/fortinet.yml +++ b/filters/fortinet/fortinet.yml @@ -1,4 +1,4 @@ -# Fortinet firewall module filter, version 3.0.1 +# Fortinet firewall module filter, version 3.0.2 # Based in docs and samples provided # # Documentations @@ -101,6 +101,66 @@ pipeline: substring: '>' fields: - log.priority + - trim: + function: prefix + substring: '"' + fields: + - log.devname + - log.devid + - log.logid + - log.type + - log.subtype + - log.eventtype + - log.level + - log.devid + - log.vd + - log.srccountry + - log.dstcountry + - log.srcintf + - log.srcintfrole + - log.dstintf + - log.dstintfrole + - log.direction + - log.poluuid + - log.policytype + - action + - log.appcat + - log.app + - log.hostname + - log.url + - log.apprisk + - log.scertcname + - log.scertissuer + - trim: + function: suffix + substring: '"' + fields: + - log.devname + - log.devid + - log.logid + - log.type + - log.subtype + - log.eventtype + - log.level + - log.devid + - log.vd + - log.srccountry + - log.dstcountry + - log.srcintf + - log.srcintfrole + - log.dstintf + - log.dstintfrole + - log.direction + - log.poluuid + - log.policytype + - action + - log.appcat + - log.app + - log.hostname + - log.url + - log.apprisk + - log.scertcname + - log.scertissuer # Adding geolocation to origin.ip - dynamic: @@ -118,6 +178,866 @@ pipeline: destination: target.geolocation where: exists("target.ip") + # Adding protocol field based on IANA protocol numbers + - add: + function: "string" + params: + key: protocol + value: "HOPOPT" + where: equals("log.proto", "0") + - add: + function: "string" + params: + key: protocol + value: "ICMP" + where: equals("log.proto", "1") + - add: + function: "string" + params: + key: protocol + value: "IGMP" + where: equals("log.proto", "2") + - add: + function: "string" + params: + key: protocol + value: "GGP" + where: equals("log.proto", "3") + - add: + function: "string" + params: + key: protocol + value: "IP-in-IP" + where: equals("log.proto", "4") + - add: + function: "string" + params: + key: protocol + value: "ST" + where: equals("log.proto", "5") + - add: + function: "string" + params: + key: protocol + value: "TCP" + where: equals("log.proto", "6") + - add: + function: "string" + params: + key: protocol + value: "CBT" + where: equals("log.proto", "7") + - add: + function: "string" + params: + key: protocol + value: "EGP" + where: equals("log.proto", "8") + - add: + function: "string" + params: + key: protocol + value: "IGP" + where: equals("log.proto", "9") + - add: + function: "string" + params: + key: protocol + value: "BBN-RCC-MON" + where: equals("log.proto", "10") + - add: + function: "string" + params: + key: protocol + value: "NVP-II" + where: equals("log.proto", "11") + - add: + function: "string" + params: + key: protocol + value: "PUP" + where: equals("log.proto", "12") + - add: + function: "string" + params: + key: protocol + value: "ARGUS" + where: equals("log.proto", "13") + - add: + function: "string" + params: + key: protocol + value: "EMCON" + where: equals("log.proto", "14") + - add: + function: "string" + params: + key: protocol + value: "XNET" + where: equals("log.proto", "15") + - add: + function: "string" + params: + key: protocol + value: "CHAOS" + where: equals("log.proto", "16") + - add: + function: "string" + params: + key: protocol + value: "UDP" + where: equals("log.proto", "17") + - add: + function: "string" + params: + key: protocol + value: "MUX" + where: equals("log.proto", "18") + - add: + function: "string" + params: + key: protocol + value: "DCN-MEAS" + where: equals("log.proto", "19") + - add: + function: "string" + params: + key: protocol + value: "HMP" + where: equals("log.proto", "20") + - add: + function: "string" + params: + key: protocol + value: "PRM" + where: equals("log.proto", "21") + - add: + function: "string" + params: + key: protocol + value: "XNS-IDP" + where: equals("log.proto", "22") + - add: + function: "string" + params: + key: protocol + value: "TRUNK-1" + where: equals("log.proto", "23") + - add: + function: "string" + params: + key: protocol + value: "TRUNK-2" + where: equals("log.proto", "24") + - add: + function: "string" + params: + key: protocol + value: "LEAF-1" + where: equals("log.proto", "25") + - add: + function: "string" + params: + key: protocol + value: "LEAF-2" + where: equals("log.proto", "26") + - add: + function: "string" + params: + key: protocol + value: "RDP" + where: equals("log.proto", "27") + - add: + function: "string" + params: + key: protocol + value: "IRTP" + where: equals("log.proto", "28") + - add: + function: "string" + params: + key: protocol + value: "ISO-TP4" + where: equals("log.proto", "29") + - add: + function: "string" + params: + key: protocol + value: "NETBLT" + where: equals("log.proto", "30") + - add: + function: "string" + params: + key: protocol + value: "MFE-NSP" + where: equals("log.proto", "31") + - add: + function: "string" + params: + key: protocol + value: "MERIT-INP" + where: equals("log.proto", "32") + - add: + function: "string" + params: + key: protocol + value: "DCCP" + where: equals("log.proto", "33") + - add: + function: "string" + params: + key: protocol + value: "3PC" + where: equals("log.proto", "34") + - add: + function: "string" + params: + key: protocol + value: "IDPR" + where: equals("log.proto", "35") + - add: + function: "string" + params: + key: protocol + value: "XTP" + where: equals("log.proto", "36") + - add: + function: "string" + params: + key: protocol + value: "DDP" + where: equals("log.proto", "37") + - add: + function: "string" + params: + key: protocol + value: "IDPR-CMTP" + where: equals("log.proto", "38") + - add: + function: "string" + params: + key: protocol + value: "TP++" + where: equals("log.proto", "39") + - add: + function: "string" + params: + key: protocol + value: "IL" + where: equals("log.proto", "40") + - add: + function: "string" + params: + key: protocol + value: "IPv6" + where: equals("log.proto", "41") + - add: + function: "string" + params: + key: protocol + value: "SDRP" + where: equals("log.proto", "42") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Route" + where: equals("log.proto", "43") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Frag" + where: equals("log.proto", "44") + - add: + function: "string" + params: + key: protocol + value: "IDRP" + where: equals("log.proto", "45") + - add: + function: "string" + params: + key: protocol + value: "RSVP" + where: equals("log.proto", "46") + - add: + function: "string" + params: + key: protocol + value: "GRE" + where: equals("log.proto", "47") + - add: + function: "string" + params: + key: protocol + value: "DSR" + where: equals("log.proto", "48") + - add: + function: "string" + params: + key: protocol + value: "BNA" + where: equals("log.proto", "49") + - add: + function: "string" + params: + key: protocol + value: "ESP" + where: equals("log.proto", "50") + - add: + function: "string" + params: + key: protocol + value: "AH" + where: equals("log.proto", "51") + - add: + function: "string" + params: + key: protocol + value: "I-NLSP" + where: equals("log.proto", "52") + - add: + function: "string" + params: + key: protocol + value: "SwIPe" + where: equals("log.proto", "53") + - add: + function: "string" + params: + key: protocol + value: "NARP" + where: equals("log.proto", "54") + - add: + function: "string" + params: + key: protocol + value: "MOBILE" + where: equals("log.proto", "55") + - add: + function: "string" + params: + key: protocol + value: "TLSP" + where: equals("log.proto", "56") + - add: + function: "string" + params: + key: protocol + value: "SKIP" + where: equals("log.proto", "57") + - add: + function: "string" + params: + key: protocol + value: "IPv6-ICMP" + where: equals("log.proto", "58") + - add: + function: "string" + params: + key: protocol + value: "IPv6-NoNxt" + where: equals("log.proto", "59") + - add: + function: "string" + params: + key: protocol + value: "IPv6-Opts" + where: equals("log.proto", "60") + - add: + function: "string" + params: + key: protocol + value: "CFTP" + where: equals("log.proto", "62") + - add: + function: "string" + params: + key: protocol + value: "SAT-EXPAK" + where: equals("log.proto", "64") + - add: + function: "string" + params: + key: protocol + value: "KRYPTOLAN" + where: equals("log.proto", "65") + - add: + function: "string" + params: + key: protocol + value: "RVD" + where: equals("log.proto", "66") + - add: + function: "string" + params: + key: protocol + value: "IPPC" + where: equals("log.proto", "67") + - add: + function: "string" + params: + key: protocol + value: "SAT-MON" + where: equals("log.proto", "69") + - add: + function: "string" + params: + key: protocol + value: "VISA" + where: equals("log.proto", "70") + - add: + function: "string" + params: + key: protocol + value: "IPCU" + where: equals("log.proto", "71") + - add: + function: "string" + params: + key: protocol + value: "CPNX" + where: equals("log.proto", "72") + - add: + function: "string" + params: + key: protocol + value: "CPHB" + where: equals("log.proto", "73") + - add: + function: "string" + params: + key: protocol + value: "WSN" + where: equals("log.proto", "74") + - add: + function: "string" + params: + key: protocol + value: "PVP" + where: equals("log.proto", "75") + - add: + function: "string" + params: + key: protocol + value: "BR-SAT-MON" + where: equals("log.proto", "76") + - add: + function: "string" + params: + key: protocol + value: "SUN-ND" + where: equals("log.proto", "77") + - add: + function: "string" + params: + key: protocol + value: "WB-MON" + where: equals("log.proto", "78") + - add: + function: "string" + params: + key: protocol + value: "WB-EXPAK" + where: equals("log.proto", "79") + - add: + function: "string" + params: + key: protocol + value: "ISO-IP" + where: equals("log.proto", "80") + - add: + function: "string" + params: + key: protocol + value: "VMTP" + where: equals("log.proto", "81") + - add: + function: "string" + params: + key: protocol + value: "SECURE-VMTP" + where: equals("log.proto", "82") + - add: + function: "string" + params: + key: protocol + value: "VINES" + where: equals("log.proto", "83") + - add: + function: "string" + params: + key: protocol + value: "IPTM" + where: equals("log.proto", "84") + - add: + function: "string" + params: + key: protocol + value: "NSFNET-IGP" + where: equals("log.proto", "85") + - add: + function: "string" + params: + key: protocol + value: "DGP" + where: equals("log.proto", "86") + - add: + function: "string" + params: + key: protocol + value: "TCF" + where: equals("log.proto", "87") + - add: + function: "string" + params: + key: protocol + value: "EIGRP" + where: equals("log.proto", "88") + - add: + function: "string" + params: + key: protocol + value: "OSPF" + where: equals("log.proto", "89") + - add: + function: "string" + params: + key: protocol + value: "Sprite-RPC" + where: equals("log.proto", "90") + - add: + function: "string" + params: + key: protocol + value: "LARP" + where: equals("log.proto", "91") + - add: + function: "string" + params: + key: protocol + value: "MTP" + where: equals("log.proto", "92") + - add: + function: "string" + params: + key: protocol + value: "AX.25" + where: equals("log.proto", "93") + - add: + function: "string" + params: + key: protocol + value: "OS" + where: equals("log.proto", "94") + - add: + function: "string" + params: + key: protocol + value: "MICP" + where: equals("log.proto", "95") + - add: + function: "string" + params: + key: protocol + value: "SCC-SP" + where: equals("log.proto", "96") + - add: + function: "string" + params: + key: protocol + value: "ETHERIP" + where: equals("log.proto", "97") + - add: + function: "string" + params: + key: protocol + value: "ENCAP" + where: equals("log.proto", "98") + - add: + function: "string" + params: + key: protocol + value: "GMTP" + where: equals("log.proto", "100") + - add: + function: "string" + params: + key: protocol + value: "IFMP" + where: equals("log.proto", "101") + - add: + function: "string" + params: + key: protocol + value: "PNNI" + where: equals("log.proto", "102") + - add: + function: "string" + params: + key: protocol + value: "PIM" + where: equals("log.proto", "103") + - add: + function: "string" + params: + key: protocol + value: "ARIS" + where: equals("log.proto", "104") + - add: + function: "string" + params: + key: protocol + value: "SCPS" + where: equals("log.proto", "105") + - add: + function: "string" + params: + key: protocol + value: "QNX" + where: equals("log.proto", "106") + - add: + function: "string" + params: + key: protocol + value: "A/N" + where: equals("log.proto", "107") + - add: + function: "string" + params: + key: protocol + value: "IPComp" + where: equals("log.proto", "108") + - add: + function: "string" + params: + key: protocol + value: "SNP" + where: equals("log.proto", "109") + - add: + function: "string" + params: + key: protocol + value: "Compaq-Peer" + where: equals("log.proto", "110") + - add: + function: "string" + params: + key: protocol + value: "IPX-in-IP" + where: equals("log.proto", "111") + - add: + function: "string" + params: + key: protocol + value: "VRRP" + where: equals("log.proto", "112") + - add: + function: "string" + params: + key: protocol + value: "PGM" + where: equals("log.proto", "113") + - add: + function: "string" + params: + key: protocol + value: "L2TP" + where: equals("log.proto", "115") + - add: + function: "string" + params: + key: protocol + value: "DDX" + where: equals("log.proto", "116") + - add: + function: "string" + params: + key: protocol + value: "IATP" + where: equals("log.proto", "117") + - add: + function: "string" + params: + key: protocol + value: "STP" + where: equals("log.proto", "118") + - add: + function: "string" + params: + key: protocol + value: "SRP" + where: equals("log.proto", "119") + - add: + function: "string" + params: + key: protocol + value: "UTI" + where: equals("log.proto", "120") + - add: + function: "string" + params: + key: protocol + value: "SMP" + where: equals("log.proto", "121") + - add: + function: "string" + params: + key: protocol + value: "SM" + where: equals("log.proto", "122") + - add: + function: "string" + params: + key: protocol + value: "PTP" + where: equals("log.proto", "123") + - add: + function: "string" + params: + key: protocol + value: "IS-IS" + where: equals("log.proto", "124") + - add: + function: "string" + params: + key: protocol + value: "FIRE" + where: equals("log.proto", "125") + - add: + function: "string" + params: + key: protocol + value: "CRTP" + where: equals("log.proto", "126") + - add: + function: "string" + params: + key: protocol + value: "CRUDP" + where: equals("log.proto", "127") + - add: + function: "string" + params: + key: protocol + value: "SSCOPMCE" + where: equals("log.proto", "128") + - add: + function: "string" + params: + key: protocol + value: "IPLT" + where: equals("log.proto", "129") + - add: + function: "string" + params: + key: protocol + value: "SPS" + where: equals("log.proto", "130") + - add: + function: "string" + params: + key: protocol + value: "PIPE" + where: equals("log.proto", "131") + - add: + function: "string" + params: + key: protocol + value: "SCTP" + where: equals("log.proto", "132") + - add: + function: "string" + params: + key: protocol + value: "FC" + where: equals("log.proto", "133") + - add: + function: "string" + params: + key: protocol + value: "RSVP-E2E-IGNORE" + where: equals("log.proto", "134") + - add: + function: "string" + params: + key: protocol + value: "Mobility-Header" + where: equals("log.proto", "135") + - add: + function: "string" + params: + key: protocol + value: "UDPLite" + where: equals("log.proto", "136") + - add: + function: "string" + params: + key: protocol + value: "MPLS-in-IP" + where: equals("log.proto", "137") + - add: + function: "string" + params: + key: protocol + value: "manet" + where: equals("log.proto", "138") + - add: + function: "string" + params: + key: protocol + value: "HIP" + where: equals("log.proto", "139") + - add: + function: "string" + params: + key: protocol + value: "Shim6" + where: equals("log.proto", "140") + - add: + function: "string" + params: + key: protocol + value: "WESP" + where: equals("log.proto", "141") + - add: + function: "string" + params: + key: protocol + value: "ROHC" + where: equals("log.proto", "142") + - add: + function: "string" + params: + key: protocol + value: "Ethernet" + where: equals("log.proto", "143") + - add: + function: "string" + params: + key: protocol + value: "AGGFRAG" + where: equals("log.proto", "144") + - add: + function: "string" + params: + key: protocol + value: "NSH" + where: equals("log.proto", "145") + - add: + function: "string" + params: + key: protocol + value: "Homa" + where: equals("log.proto", "146") + - add: + function: "string" + params: + key: protocol + value: "BIT-EMU" + where: equals("log.proto", "147") + # Removing unused fields - delete: fields: From 885a973adc8eae3a6b23559a0c32e5a713673be9 Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Wed, 18 Feb 2026 12:16:10 -0500 Subject: [PATCH 109/240] fix(filter): update IBM AS 400 filter with enhanced JSON parsing and field mappings --- filters/ibm/ibm_as_400.yml | 81 +++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/filters/ibm/ibm_as_400.yml b/filters/ibm/ibm_as_400.yml index 40ae2d6d3..387347bdb 100644 --- a/filters/ibm/ibm_as_400.yml +++ b/filters/ibm/ibm_as_400.yml @@ -1,58 +1,83 @@ -# IBM AS 400 filter version 3.0.1 -# Support Java Collector Syslog messsages +# IBM AS 400 filter version 4.0.0 +# Support for JT400 Java Collector with full field mapping pipeline: - dataTypes: - ibm-as400 steps: + # ======================================== + # PHASE 1: EXTRACTION + # ======================================== + + # Parse JSON logs from Java collector - json: source: raw - where: contains("raw", "\"msg\":") && contains("raw", "\"args\":") + where: 'startsWith("raw", "{") && endsWith("raw", "}")' + # Fallback: Parse non-JSON logs (raw syslog format) - grok: source: raw patterns: - fieldName: log.message pattern: '{{.greedy}}' - where: '!contains("raw", "\"msg\":") || !contains("raw", "\"args\":")' + where: '!startsWith("raw", "{") && !endsWith("raw", "}")' + # ======================================== + # PHASE 2: NORMALIZATION (Standard Schema) + # ======================================== + + # Map device timestamp (critical for correlation) - rename: - from: - - log.hostname + from: log.timestampIso + to: deviceTime + where: exists("log.timestampIso") + + # Map action/event classification + - rename: + from: log.eventType + to: action + where: exists("log.eventType") + + # Map severity level + - rename: + from: log.severityLabel + to: severity + where: exists("log.severityLabel") + + # Map origin side attributes + - rename: + from: log.hostname to: origin.host + where: exists("log.hostname") - rename: - from: - - log.sourceIp - to: origin.ip + from: log.jobUser + to: origin.user + where: exists("log.jobUser") - rename: - from: - - log.destinationIp - to: target.ip + from: log.jobName + to: origin.process + where: exists("log.jobName") - rename: - from: - - log.file - to: origin.file + from: log.sourceIp + to: origin.ip + where: exists("log.sourceIp") - rename: - from: - - log.severityText - to: severity + from: log.file + to: origin.file + where: exists("log.file") - # Adding geolocation to origin.ip + # ======================================== + # PHASE 3: ENRICHMENT + # ======================================== + + # Add geolocation for source IP - dynamic: plugin: com.utmstack.geolocation params: source: origin.ip destination: origin.geolocation - where: exists("origin.ip") - - # Adding geolocation to target.ip - - dynamic: - plugin: com.utmstack.geolocation - params: - source: target.ip - destination: target.geolocation - where: exists("target.ip") \ No newline at end of file + where: exists("origin.ip") \ No newline at end of file From 15d8330fd39e361c7a3a7bcc9566235c27256fef Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Wed, 18 Feb 2026 13:43:21 -0500 Subject: [PATCH 110/240] fix(filter): update IBM AS 400 filter --- filters/ibm/ibm_as_400.yml | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/filters/ibm/ibm_as_400.yml b/filters/ibm/ibm_as_400.yml index 387347bdb..11522baef 100644 --- a/filters/ibm/ibm_as_400.yml +++ b/filters/ibm/ibm_as_400.yml @@ -9,10 +9,14 @@ pipeline: # PHASE 1: EXTRACTION # ======================================== + # Drop non-AS400 logs (internal system logs, noise) + - drop: + where: 'startsWith("raw", "[") || contains("raw", "RunExecutor") || contains("raw", "ETL pool")' + # Parse JSON logs from Java collector - json: source: raw - where: 'startsWith("raw", "{") && endsWith("raw", "}")' + where: 'startsWith("raw", "{")' # Fallback: Parse non-JSON logs (raw syslog format) - grok: @@ -20,53 +24,54 @@ pipeline: patterns: - fieldName: log.message pattern: '{{.greedy}}' - where: '!startsWith("raw", "{") && !endsWith("raw", "}")' + where: '!startsWith("raw", "{")' # ======================================== # PHASE 2: NORMALIZATION (Standard Schema) # ======================================== - - # Map device timestamp (critical for correlation) - - rename: - from: log.timestampIso - to: deviceTime - where: exists("log.timestampIso") # Map action/event classification - rename: - from: log.eventType + from: + - log.eventType to: action where: exists("log.eventType") # Map severity level - rename: - from: log.severityLabel + from: + - log.severityLabel to: severity where: exists("log.severityLabel") # Map origin side attributes - rename: - from: log.hostname + from: + - log.hostname to: origin.host where: exists("log.hostname") - rename: - from: log.jobUser + from: + - log.jobUser to: origin.user where: exists("log.jobUser") - rename: - from: log.jobName + from: + - log.jobName to: origin.process where: exists("log.jobName") - rename: - from: log.sourceIp + from: + - log.sourceIp to: origin.ip where: exists("log.sourceIp") - rename: - from: log.file + from: + - log.file to: origin.file where: exists("log.file") From aea60bbe574e7e7941ed761a9d0ccd85a41f12e9 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 18 Feb 2026 14:06:10 -0600 Subject: [PATCH 111/240] chore(cleanup): remove unused integrations (Redis, Nginx, PostgreSQL, Apache, MySQL, MongoDB, Elastic, Logstash, Kibana, Kafka, NATS, Traefik, Audit, HAP, IIS, OSQuery) --- .../20260218001_remove_redis_integration.xml | 40 +++++++++++++++++++ .../20260218002_remove_nginx_integration.xml | 40 +++++++++++++++++++ ...60218003_remove_postgresql_integration.xml | 40 +++++++++++++++++++ .../20260218004_remove_apache_integration.xml | 40 +++++++++++++++++++ .../20260218005_remove_mysql_integration.xml | 40 +++++++++++++++++++ ...20260218006_remove_mongodb_integration.xml | 40 +++++++++++++++++++ ...20260218007_remove_elastic_integration.xml | 40 +++++++++++++++++++ ...0260218008_remove_logstash_integration.xml | 40 +++++++++++++++++++ .../20260218009_remove_kibana_integration.xml | 40 +++++++++++++++++++ .../20260218010_remove_kafka_integration.xml | 40 +++++++++++++++++++ .../20260218011_remove_nats_integration.xml | 40 +++++++++++++++++++ ...20260218012_remove_traefik_integration.xml | 40 +++++++++++++++++++ .../20260218013_remove_audit_integration.xml | 40 +++++++++++++++++++ .../20260218014_remove_hap_integration.xml | 40 +++++++++++++++++++ .../20260218015_remove_iis_integration.xml | 40 +++++++++++++++++++ ...0260218016_remove_os_query_integration.xml | 40 +++++++++++++++++++ .../resources/config/liquibase/master.xml | 32 +++++++++++++++ 17 files changed, 672 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218001_remove_redis_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218002_remove_nginx_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218003_remove_postgresql_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218004_remove_apache_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218005_remove_mysql_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218006_remove_mongodb_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218007_remove_elastic_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218008_remove_logstash_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218009_remove_kibana_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218010_remove_kafka_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218011_remove_nats_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218012_remove_traefik_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218013_remove_audit_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218014_remove_hap_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218015_remove_iis_integration.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260218016_remove_os_query_integration.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218001_remove_redis_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218001_remove_redis_integration.xml new file mode 100644 index 000000000..56b04a54a --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218001_remove_redis_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_redis(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218002_remove_nginx_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218002_remove_nginx_integration.xml new file mode 100644 index 000000000..92547ba6d --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218002_remove_nginx_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_nginx(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218003_remove_postgresql_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218003_remove_postgresql_integration.xml new file mode 100644 index 000000000..ce4554d1e --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218003_remove_postgresql_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_postgresql(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218004_remove_apache_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218004_remove_apache_integration.xml new file mode 100644 index 000000000..0d9c2acea --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218004_remove_apache_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_apache(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218005_remove_mysql_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218005_remove_mysql_integration.xml new file mode 100644 index 000000000..9b23fd9f0 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218005_remove_mysql_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_mysql(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218006_remove_mongodb_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218006_remove_mongodb_integration.xml new file mode 100644 index 000000000..2f2b334d0 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218006_remove_mongodb_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_mongodb(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218007_remove_elastic_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218007_remove_elastic_integration.xml new file mode 100644 index 000000000..78c192062 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218007_remove_elastic_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_elasticsearch(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218008_remove_logstash_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218008_remove_logstash_integration.xml new file mode 100644 index 000000000..700fd1663 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218008_remove_logstash_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_logstash(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218009_remove_kibana_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218009_remove_kibana_integration.xml new file mode 100644 index 000000000..4a9b51019 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218009_remove_kibana_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_kibana(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218010_remove_kafka_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218010_remove_kafka_integration.xml new file mode 100644 index 000000000..bddee7300 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218010_remove_kafka_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_kafka(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218011_remove_nats_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218011_remove_nats_integration.xml new file mode 100644 index 000000000..920edef51 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218011_remove_nats_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_nats(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218012_remove_traefik_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218012_remove_traefik_integration.xml new file mode 100644 index 000000000..d463ee01b --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218012_remove_traefik_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_traefik(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218013_remove_audit_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218013_remove_audit_integration.xml new file mode 100644 index 000000000..235698d86 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218013_remove_audit_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_ad_audit(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218014_remove_hap_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218014_remove_hap_integration.xml new file mode 100644 index 000000000..07ff2c475 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218014_remove_hap_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_hap(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218015_remove_iis_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218015_remove_iis_integration.xml new file mode 100644 index 000000000..2b691f116 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218015_remove_iis_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_iis(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260218016_remove_os_query_integration.xml b/backend/src/main/resources/config/liquibase/changelog/20260218016_remove_os_query_integration.xml new file mode 100644 index 000000000..d5c178137 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260218016_remove_os_query_integration.xml @@ -0,0 +1,40 @@ + + + + + + + + + DROP FUNCTION IF EXISTS register_integration_osquery(integer); + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 8fd4434f5..7a162fe6e 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -433,6 +433,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9b75b97309b485d694564bcfe943ba5c1a0c19bd Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 18 Feb 2026 14:36:07 -0600 Subject: [PATCH 112/240] chore(cleanup): remove integrations (Redis, Nginx, PostgreSQL, Apache, MySQL, MongoDB, Elastic, Logstash, Kibana, Kafka, NATS, Traefik, Audit, HAP, IIS, OSQuery) --- .../factory/impl/ModuleApache.java | 48 ------------------- .../factory/impl/ModuleAuditD.java | 48 ------------------- .../factory/impl/ModuleElasticsearch.java | 48 ------------------- .../factory/impl/ModuleHaProxy.java | 48 ------------------- .../factory/impl/ModuleIIS.java | 48 ------------------- .../factory/impl/ModuleKafka.java | 48 ------------------- .../factory/impl/ModuleKibana.java | 48 ------------------- .../factory/impl/ModuleLogstash.java | 48 ------------------- .../factory/impl/ModuleMongoDb.java | 48 ------------------- .../factory/impl/ModuleMySql.java | 48 ------------------- .../factory/impl/ModuleNats.java | 48 ------------------- .../factory/impl/ModuleNginx.java | 48 ------------------- .../factory/impl/ModuleOsQuery.java | 48 ------------------- .../factory/impl/ModulePostgreSql.java | 48 ------------------- .../factory/impl/ModuleRedis.java | 48 ------------------- .../factory/impl/ModuleTraefik.java | 48 ------------------- 16 files changed, 768 deletions(-) delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleApache.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleAuditD.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleElasticsearch.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleHaProxy.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleIIS.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKafka.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKibana.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleLogstash.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMongoDb.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMySql.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNats.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNginx.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleOsQuery.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModulePostgreSql.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleRedis.java delete mode 100644 backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleTraefik.java diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleApache.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleApache.java deleted file mode 100644 index 99926b557..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleApache.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleApache implements IModule { - private static final String CLASSNAME = "ModuleApache"; - - private final UtmModuleService moduleService; - - public ModuleApache(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.APACHE); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.APACHE; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleAuditD.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleAuditD.java deleted file mode 100644 index f284c3cd0..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleAuditD.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleAuditD implements IModule { - private static final String CLASSNAME = "ModuleAuditD"; - - private final UtmModuleService moduleService; - - public ModuleAuditD(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.AUDITD); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.AUDITD; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleElasticsearch.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleElasticsearch.java deleted file mode 100644 index 2027641cb..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleElasticsearch.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleElasticsearch implements IModule { - private static final String CLASSNAME = "ModuleElasticsearch"; - - private final UtmModuleService moduleService; - - public ModuleElasticsearch(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.ELASTICSEARCH); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.ELASTICSEARCH; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleHaProxy.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleHaProxy.java deleted file mode 100644 index 82af81dbc..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleHaProxy.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleHaProxy implements IModule { - private static final String CLASSNAME = "ModuleHaProxy"; - - private final UtmModuleService moduleService; - - public ModuleHaProxy(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.HAPROXY); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.HAPROXY; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleIIS.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleIIS.java deleted file mode 100644 index ae40db0a2..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleIIS.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleIIS implements IModule { - private static final String CLASSNAME = "ModuleIIS"; - - private final UtmModuleService moduleService; - - public ModuleIIS(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.IIS); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.IIS; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKafka.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKafka.java deleted file mode 100644 index fb79ed7bb..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKafka.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleKafka implements IModule { - private static final String CLASSNAME = "ModuleKafka"; - - private final UtmModuleService moduleService; - - public ModuleKafka(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.KAFKA); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.KAFKA; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKibana.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKibana.java deleted file mode 100644 index 37a6b8583..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleKibana.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleKibana implements IModule { - private static final String CLASSNAME = "ModuleKibana"; - - private final UtmModuleService moduleService; - - public ModuleKibana(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.KIBANA); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.KIBANA; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleLogstash.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleLogstash.java deleted file mode 100644 index cef8dde4f..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleLogstash.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleLogstash implements IModule { - private static final String CLASSNAME = "ModuleLogstash"; - - private final UtmModuleService moduleService; - - public ModuleLogstash(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.LOGSTASH); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.LOGSTASH; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMongoDb.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMongoDb.java deleted file mode 100644 index 66e55819a..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMongoDb.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleMongoDb implements IModule { - private static final String CLASSNAME = "ModuleMongoDb"; - - private final UtmModuleService moduleService; - - public ModuleMongoDb(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.MONGODB); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.MONGODB; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMySql.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMySql.java deleted file mode 100644 index c0728dc34..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleMySql.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleMySql implements IModule { - private static final String CLASSNAME = "ModuleMySql"; - - private final UtmModuleService moduleService; - - public ModuleMySql(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.MYSQL); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.MYSQL; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNats.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNats.java deleted file mode 100644 index 8bf67719c..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNats.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleNats implements IModule { - private static final String CLASSNAME = "ModuleNats"; - - private final UtmModuleService moduleService; - - public ModuleNats(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.NATS); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.NATS; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNginx.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNginx.java deleted file mode 100644 index 72c865ac1..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleNginx.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleNginx implements IModule { - private static final String CLASSNAME = "ModuleNginx"; - - private final UtmModuleService moduleService; - - public ModuleNginx(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.NGINX); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.NGINX; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleOsQuery.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleOsQuery.java deleted file mode 100644 index d8aa48f02..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleOsQuery.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleOsQuery implements IModule { - private static final String CLASSNAME = "ModuleOsQuery"; - - private final UtmModuleService moduleService; - - public ModuleOsQuery(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.OSQUERY); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.OSQUERY; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModulePostgreSql.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModulePostgreSql.java deleted file mode 100644 index 095990766..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModulePostgreSql.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModulePostgreSql implements IModule { - private static final String CLASSNAME = "ModulePostgreSql"; - - private final UtmModuleService moduleService; - - public ModulePostgreSql(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.POSTGRESQL); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.POSTGRESQL; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleRedis.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleRedis.java deleted file mode 100644 index 516ec2cff..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleRedis.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleRedis implements IModule { - private static final String CLASSNAME = "ModuleRedis"; - - private final UtmModuleService moduleService; - - public ModuleRedis(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.REDIS); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.REDIS; - } -} diff --git a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleTraefik.java b/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleTraefik.java deleted file mode 100644 index 2c45d2306..000000000 --- a/backend/src/main/java/com/park/utmstack/domain/application_modules/factory/impl/ModuleTraefik.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.park.utmstack.domain.application_modules.factory.impl; - -import com.park.utmstack.domain.application_modules.UtmModule; -import com.park.utmstack.domain.application_modules.enums.ModuleName; -import com.park.utmstack.domain.application_modules.factory.IModule; -import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; -import com.park.utmstack.domain.application_modules.types.ModuleRequirement; -import com.park.utmstack.service.application_modules.UtmModuleService; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; - -@Component -public class ModuleTraefik implements IModule { - private static final String CLASSNAME = "ModuleTraefik"; - - private final UtmModuleService moduleService; - - public ModuleTraefik(UtmModuleService moduleService) { - this.moduleService = moduleService; - } - - @Override - public UtmModule getDetails(Long serverId) throws Exception { - final String ctx = CLASSNAME + ".getDetails"; - try { - return moduleService.findByServerIdAndModuleName(serverId, ModuleName.TRAEFIK); - } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); - } - } - - @Override - public List checkRequirements(Long serverId) throws Exception { - return Collections.emptyList(); - } - - @Override - public List getConfigurationKeys(Long groupId) throws Exception { - return Collections.emptyList(); - } - - @Override - public ModuleName getName() { - return ModuleName.TRAEFIK; - } -} From 03938884af908f15347b465a41d3c0ea60d8bbcf Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 18 Feb 2026 15:00:21 -0600 Subject: [PATCH 113/240] feat(logstash): enhance logstash stats retrieval with improved error handling and pipeline status management --- .../UtmLogstashPipelineService.java | 89 ++++++++++--------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/logstash_pipeline/UtmLogstashPipelineService.java b/backend/src/main/java/com/park/utmstack/service/logstash_pipeline/UtmLogstashPipelineService.java index 125926afb..4b2383177 100644 --- a/backend/src/main/java/com/park/utmstack/service/logstash_pipeline/UtmLogstashPipelineService.java +++ b/backend/src/main/java/com/park/utmstack/service/logstash_pipeline/UtmLogstashPipelineService.java @@ -20,6 +20,7 @@ import com.park.utmstack.service.logstash_pipeline.response.pipeline.PipelineStats; import com.park.utmstack.service.logstash_pipeline.response.statistic.StatisticDocument; import com.park.utmstack.service.web_clients.rest_template.RestTemplateService; +import com.park.utmstack.util.exceptions.ApiException; import com.park.utmstack.web.rest.vm.UtmLogstashPipelineVM; import com.utmstack.opensearch_connector.parsers.TermAggregateParser; import com.utmstack.opensearch_connector.types.BucketAggregation; @@ -38,6 +39,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -267,57 +269,56 @@ public ApiEngineResponse logstashJvmApiResponse() { * Getting active pipelines stats from DB, general jvm stats from logstash */ public ApiStatsResponse getLogstashStats() throws Exception { - final String ctx = CLASSNAME + ".getLogstashStats"; - ApiStatsResponse statsResponse = new ApiStatsResponse(); + final String ctx = CLASSNAME + ".getLogstashStats"; - // Variables used to set the general pipeline's status - AtomicInteger activePipelinesCount = new AtomicInteger(); - AtomicInteger upPipelinesCount = new AtomicInteger(); + try { + ApiStatsResponse statsResponse = new ApiStatsResponse(); boolean isCorrelationUp = isEngineUp(); - try { - // Getting Jvm information (not used) - ApiEngineResponse jvmData = logstashJvmApiResponse(); - if (jvmData != null) { - statsResponse.setGeneral(jvmData); - } - // List to store stats mapped from DB - List infoStats; - - // Getting the active pipelines statistics - infoStats = activePipelinesList().stream().map(activePip -> { - - // Calculating stats for pipelines - // Setting stats for non-logstash pipelines (correlation engine) - if (isCorrelationUp) { - activePipelinesCount.getAndIncrement(); // Total pipelines that have to be active - if (activePip.getPipelineStatus().equals(PipelineStatus.PIPELINE_STATUS_UP.get())) { - upPipelinesCount.getAndIncrement(); - } - } else { - activePip.setPipelineStatus(PipelineStatus.PIPELINE_STATUS_DOWN.get()); - } - // } - // Mapping stats from DB pipeline - return PipelineStats.getPipelineStats(activePip); - }).collect(Collectors.toList()); - - // Setting the final global status of pipelines - if (isCorrelationUp) { - if (upPipelinesCount.get() == 0) { - jvmData.setStatus(PipelineStatus.ENGINE_STATUS_RED.get()); - } else if (upPipelinesCount.get() == activePipelinesCount.get()) { - jvmData.setStatus(PipelineStatus.ENGINE_STATUS_GREEN.get()); - } else { - jvmData.setStatus(PipelineStatus.ENGINE_STATUS_YELLOW.get()); - } + ApiEngineResponse jvmData = logstashJvmApiResponse(); + if (jvmData != null) { + statsResponse.setGeneral(jvmData); + } + + List activePipelines = activePipelinesList(); + + if (!isCorrelationUp) { + activePipelines.forEach(p -> + p.setPipelineStatus(PipelineStatus.PIPELINE_STATUS_DOWN.get()) + ); + } + + List pipelineStatsList = activePipelines.stream() + .map(PipelineStats::getPipelineStats) + .sorted( Comparator.comparing(PipelineStats::getPipelineStatus).reversed()) + .collect(Collectors.toList()); + + statsResponse.setPipelines(pipelineStatsList); + + if (isCorrelationUp && jvmData != null) { + long upCount = activePipelines.stream() + .filter(p -> PipelineStatus.PIPELINE_STATUS_UP.get() + .equals(p.getPipelineStatus())) + .count(); + + int totalCount = activePipelines.size(); + + if (upCount == 0) { + jvmData.setStatus(PipelineStatus.ENGINE_STATUS_RED.get()); + } else if (upCount == totalCount) { + jvmData.setStatus(PipelineStatus.ENGINE_STATUS_GREEN.get()); + } else { + jvmData.setStatus(PipelineStatus.ENGINE_STATUS_YELLOW.get()); } - statsResponse.setPipelines(infoStats); - } catch (Exception ex) { - throw new Exception(ctx + ": " + ex.getMessage()); } + return statsResponse; + + } catch (Exception ex) { + log.error("{}: An error occurred while fetching logstash stats: {}", ctx, ex.getMessage(), ex); + throw new ApiException(String.format("%s: An error occurred while fetching logstash stats", ctx), HttpStatus.INTERNAL_SERVER_ERROR); } +} /** * Method to set the DB pipelines status From 34d8fadf70da7e356f910d34283a6bdaa15b695e Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Fri, 20 Feb 2026 12:25:05 -0500 Subject: [PATCH 114/240] fix(system_linux): update filter with enhanced JSON parsing and field normalization --- filters/filebeat/system_linux_module.yml | 393 ++++++++++++++++++++--- 1 file changed, 344 insertions(+), 49 deletions(-) diff --git a/filters/filebeat/system_linux_module.yml b/filters/filebeat/system_linux_module.yml index ba96d3e21..5bbbac822 100644 --- a/filters/filebeat/system_linux_module.yml +++ b/filters/filebeat/system_linux_module.yml @@ -1,79 +1,374 @@ -# System linux filter, version 3.0.0 -# Fields based on https://www.elastic.co/guide/en/beats/filebeat/7.13/filebeat-module-system.html -# and filebeat fields.yml version 7.13.4 oss -# As the docs says this module work with one event per line, filebeat must ensure to send one event per line. - -# Filter Input requirements -> fileset: datatype -# syslog: plain text +# System Linux filter version 4.0.0 +# Support for systemd/journald JSON format from filebeat/journald +# Converts SCREAMINGSNAKECASE and snakecase to camelCase +# Maps to UTMStack Standard Event Schema +# Optimized: Direct mapping to standard schema (no intermediate steps) + pipeline: - dataTypes: - linux steps: + # ======================================== + # PHASE 1: EXTRACTION + # ======================================== + + # Parse JSON from systemd/journald - json: source: raw + where: 'startsWith("raw", "{")' + + # ======================================== + # PHASE 2: FIELD NORMALIZATION (camelCase conversion) + # ======================================== + + # Convert SCREAMINGSNAKECASE to camelCase - rename: from: - - log.url - to: origin.url + - log.MESSAGE + to: log.message + where: exists("log.MESSAGE") + - rename: from: - - log.log.file.path - to: origin.file + - log.PRIORITY + to: log.priority + where: exists("log.PRIORITY") + - rename: from: - - log.host.ip - to: log.local.ips + - log.SYSLOGIDENTIFIER + to: log.syslogIdentifier + where: exists("log.SYSLOGIDENTIFIER") + - rename: from: - - log.host.mac - to: log.local.macs + - log.SYSLOGTIMESTAMP + to: log.syslogTimestamp + where: exists("log.SYSLOGTIMESTAMP") + - rename: from: - - log.host.hostname - to: origin.host + - log.SYSLOGFACILITY + to: log.syslogFacility + where: exists("log.SYSLOGFACILITY") + - rename: from: - - log.host.name - to: origin.user + - log.SYSLOGPID + to: log.syslogPid + where: exists("log.SYSLOGPID") + + # Convert snakecase to camelCase (only for fields staying in log.*) + - rename: + from: + - log.PID + to: log.pid + where: exists("log.PID") + + - rename: + from: + - log.UID + to: log.uid + where: exists("log.UID") + + - rename: + from: + - log.GID + to: log.gid + where: exists("log.GID") + + - rename: + from: + - log.TID + to: log.tid + where: exists("log.TID") + + - rename: + from: + - log.EXE + to: log.exe + where: exists("log.EXE") + + - rename: + from: + - log.UNIT + to: log.unit + where: exists("log.UNIT") + + - rename: + from: + - log.SYSTEMDUNIT + to: log.systemdUnit + where: exists("log.SYSTEMDUNIT") + + - rename: + from: + - log.SYSTEMDSLICE + to: log.systemdSlice + where: exists("log.SYSTEMDSLICE") + + - rename: + from: + - log.SYSTEMDUSERSLICE + to: log.systemdUserSlice + where: exists("log.SYSTEMDUSERSLICE") + + - rename: + from: + - log.SYSTEMDSESSION + to: log.systemdSession + where: exists("log.SYSTEMDSESSION") + + - rename: + from: + - log.SESSIONID + to: log.sessionId + where: exists("log.SESSIONID") + + - rename: + from: + - log.LEADER + to: log.leader + where: exists("log.LEADER") + + - rename: + from: + - log.SYSTEMDOWNERUID + to: log.systemdOwnerUid + where: exists("log.SYSTEMDOWNERUID") + + - rename: + from: + - log.SYSTEMDCGROUP + to: log.systemdCgroup + where: exists("log.SYSTEMDCGROUP") + + - rename: + from: + - log.BOOTID + to: log.bootId + where: exists("log.BOOTID") + + - rename: + from: + - log.MACHINEID + to: log.machineId + where: exists("log.MACHINEID") + + - rename: + from: + - log.TRANSPORT + to: log.transport + where: exists("log.TRANSPORT") + + - rename: + from: + - log.SELINUXCONTEXT + to: log.selinuxContext + where: exists("log.SELINUXCONTEXT") + - rename: from: - - log.host.id - to: log.hostId + - log.AUDITSESSION + to: log.auditSession + where: exists("log.AUDITSESSION") + - rename: from: - - log.event.dataset - to: action + - log.AUDITLOGINUID + to: log.auditLoginUid + where: exists("log.AUDITLOGINUID") + - rename: from: - - log.agent.version - to: log.agentVersion + - log.CAPEFFECTIVE + to: log.capEffective + where: exists("log.CAPEFFECTIVE") + - rename: from: - - log.host.os.kernel - to: log.osVersion + - log.REALTIMETIMESTAMP + to: log.realtimeTimestamp + where: exists("log.REALTIMETIMESTAMP") + + - rename: + from: + - log.SOURCEREALTIMETIMESTAMP + to: log.sourceRealtimeTimestamp + where: exists("log.SOURCEREALTIMETIMESTAMP") + + - rename: + from: + - log.MONOTONICTIMESTAMP + to: log.monotonicTimestamp + where: exists("log.MONOTONICTIMESTAMP") + + - rename: + from: + - log.CURSOR + to: log.cursor + where: exists("log.CURSOR") + + - rename: + from: + - log.SEQNUM + to: log.seqnum + where: exists("log.SEQNUM") + + - rename: + from: + - log.SEQNUMID + to: log.seqnumId + where: exists("log.SEQNUMID") + + - rename: + from: + - log.RUNTIMESCOPE + to: log.runtimeScope + where: exists("log.RUNTIMESCOPE") + + - rename: + from: + - log.STREAMID + to: log.streamId + where: exists("log.STREAMID") + + - rename: + from: + - log.SYSTEMDINVOCATIONID + to: log.systemdInvocationId + where: exists("log.SYSTEMDINVOCATIONID") + + - rename: + from: + - log.CODEFILE + to: log.codeFile + where: exists("log.CODEFILE") + + - rename: + from: + - log.CODELINE + to: log.codeLine + where: exists("log.CODELINE") + + - rename: + from: + - log.CODEFUNC + to: log.codeFunc + where: exists("log.CODEFUNC") + + - rename: + from: + - log.INVOCATIONID + to: log.invocationId + where: exists("log.INVOCATIONID") + + - rename: + from: + - log.JOBID + to: log.jobId + where: exists("log.JOBID") + + - rename: + from: + - log.JOBRESULT + to: actionResult + where: exists("log.JOBRESULT") + + - rename: + from: + - log.JOBTYPE + to: log.jobType + where: exists("log.JOBTYPE") + + - rename: + from: + - log.MESSAGEID + to: log.messageId + where: exists("log.MESSAGEID") + + # ======================================== + # PHASE 3: STANDARD SCHEMA MAPPING + # ======================================== + + # Map directly to Standard Event Schema (no intermediate camelCase step) + - rename: + from: + - log.HOSTNAME + to: origin.host + where: exists("log.HOSTNAME") + + - rename: + from: + - log.USERID + to: origin.user + where: exists("log.USERID") + - rename: from: - - log.host.os.type - to: log.osType + - log.COMM + to: origin.process + where: exists("log.COMM") + - rename: from: - - log.host.architecture - to: log.cpuArchitecture - - cast: - to: '[]string' - fields: - - log.local.ips - - cast: - to: '[]string' - fields: - - log.local.macs - - delete: - fields: - - log.service - - log.metadata - - log.agent - - log.host - - log.event - - log.ecs - - log.log - - log.fileset \ No newline at end of file + - log.CMDLINE + to: origin.command + where: exists("log.CMDLINE") + + # Map syslog priority (0-7) to severity labels + - add: + function: string + params: + key: severity + value: "emergency" + where: 'equals("log.priority", "0")' + + - add: + function: string + params: + key: severity + value: "alert" + where: 'equals("log.priority", "1")' + + - add: + function: string + params: + key: severity + value: "critical" + where: 'equals("log.priority", "2")' + + - add: + function: string + params: + key: severity + value: "error" + where: 'equals("log.priority", "3")' + + - add: + function: string + params: + key: severity + value: "warning" + where: 'equals("log.priority", "4")' + + - add: + function: string + params: + key: severity + value: "notice" + where: 'equals("log.priority", "5")' + + - add: + function: string + params: + key: severity + value: "info" + where: 'equals("log.priority", "6")' + + - add: + function: string + params: + key: severity + value: "debug" + where: 'equals("log.priority", "7")' \ No newline at end of file From b4466fab254e63b685c50aebb6ad405326e214dd Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 20 Feb 2026 13:59:50 -0600 Subject: [PATCH 115/240] feat(filter): add Linux filter update with enhanced JSON parsing and field normalization --- .../20260220001_update_filter_linux.xml | 393 ++++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 2 files changed, 395 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260220001_update_filter_linux.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260220001_update_filter_linux.xml b/backend/src/main/resources/config/liquibase/changelog/20260220001_update_filter_linux.xml new file mode 100644 index 000000000..7169e2b19 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260220001_update_filter_linux.xml @@ -0,0 +1,393 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 7a162fe6e..7ad19b973 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -465,6 +465,8 @@ + + From ec0cfedb4e2b3345ed6053d16a67c52cdf20c22d Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 20 Feb 2026 15:05:45 -0600 Subject: [PATCH 116/240] feat(visualization): add update for Linux visualizations to normalize field names and improve dataset consistency --- ...0260220002_update_linux_visualizations.xml | 154 ++++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 2 files changed, 156 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260220002_update_linux_visualizations.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260220002_update_linux_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260220002_update_linux_visualizations.xml new file mode 100644 index 000000000..60c0959b8 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260220002_update_linux_visualizations.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 7ad19b973..b9fbb258a 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -467,6 +467,8 @@ + + From ff25941e14716d496922c8124ddfba9c97ac2a15 Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Mon, 23 Feb 2026 18:23:33 +0300 Subject: [PATCH 117/240] feat(windows): update windows filter --- filters/windows/windows-events.yml | 503 +++++++++++++---------------- 1 file changed, 221 insertions(+), 282 deletions(-) diff --git a/filters/windows/windows-events.yml b/filters/windows/windows-events.yml index 2457dea08..534a26076 100644 --- a/filters/windows/windows-events.yml +++ b/filters/windows/windows-events.yml @@ -1,4 +1,4 @@ -# Windows_Agent filter, version 3.0.4 +# Windows_Agent filter, version 3.1.0 # Based on winlogbeat fields, reference [8.15] # See https://www.elastic.co/guide/en/beats/winlogbeat/current/exported-fields-winlog.html @@ -12,333 +12,277 @@ pipeline: # Renaming useful fields - rename: from: - - log.host.ip - to: log.origin.ips - - - rename: - from: - - log.host.mac - to: log.origin.macs - - - rename: - from: - - log.event.action - to: log.action - - - rename: - from: - - log.event.outcome - to: actionResult - - - rename: - from: - - log.host.hostname - to: origin.host - - - rename: - from: - - log.event.created - to: log.deviceTime - - - rename: - from: - - log.host.os.name - to: log.os - - - rename: - from: - - log.host.os.kernel - to: log.osVersion - - - rename: - from: - - log.host.architecture - to: log.cpuArchitecture - - - rename: - from: - - log.winlog.provider_guid + - log.providerguid to: log.providerGuid - rename: from: - - log.winlog.event_data.PrivilegeList - to: log.winlogEventDataPrivilegeList + - log.data.PrivilegeList + to: log.eventDataPrivilegeList - rename: from: - - log.winlog.event_data.ServiceName - to: log.winlogEventDataServiceName + - log.data.ServiceName + to: log.eventDataServiceName - rename: from: - - log.winlog.event_data.SubjectDomainName - to: log.winlogEventDataSubjectDomainName + - log.data.SubjectDomainName + to: log.eventDataSubjectDomainName - rename: from: - - log.winlog.event_data.SubjectLogonId - to: log.winlogEventDataSubjectLogonId + - log.data.SubjectLogonId + to: log.eventDataSubjectLogonId - rename: from: - - log.winlog.event_data.SubjectUserName - to: log.winlogEventDataSubjectUserName + - log.data.SubjectUserName + to: log.eventDataSubjectUserName - rename: from: - - log.winlog.event_data.SubjectUserSid - to: log.winlogEventDataSubjectUserSid + - log.data.SubjectUserSid + to: log.eventDataSubjectUserSid - rename: from: - - log.winlog.event_data.SubjectUserSid - to: log.winlogEventDataSubjectUserSid + - log.data.SubjectUserSid + to: log.eventDataSubjectUserSid - rename: from: - - log.winlog.event_data.PrivilegeList - to: log.winlogEventDataPrivilegeList + - log.data.PrivilegeList + to: log.eventDataPrivilegeList - rename: from: - - log.winlog.event_data.ClientProcessId - to: log.winlogEventDataClientProcessId + - log.data.ClientProcessId + to: log.eventDataClientProcessId - rename: from: - - log.winlog.event_data.Flags - to: log.winlogEventDataFlags + - log.data.Flags + to: log.eventDataFlags - rename: from: - - log.winlog.event_data.Identity - to: log.winlogEventDataIdentity + - log.data.Identity + to: log.eventDataIdentity - rename: from: - - log.winlog.event_data.ProcessCreationTime - to: log.winlogEventDataProcessCreationTime + - log.data.ProcessCreationTime + to: log.eventDataProcessCreationTime - rename: from: - - log.winlog.event_id - to: log.winlogEventId + - log.id + to: log.eventId - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.ReturnCode - to: log.winlogEventDataReturnCode + - log.data.ReturnCode + to: log.eventDataReturnCode - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.Schema - to: log.winlogEventDataSchema + - log.data.Schema + to: log.eventDataSchema - rename: from: - - log.winlog.event_data.SchemaFriendlyName - to: log.winlogEventDataSchemaFriendlyName + - log.data.SchemaFriendlyName + to: log.eventDataSchemaFriendlyName - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.AuthenticationPackageName - to: log.winlogEventDataAuthenticationPackageName + - log.data.AuthenticationPackageName + to: log.eventDataAuthenticationPackageName - rename: from: - - log.winlog.event_data.ElevatedToken - to: log.winlogEventDataElevatedToken + - log.data.ElevatedToken + to: log.eventDataElevatedToken - rename: from: - - log.winlog.event_data.ImpersonationLevel - to: log.winlogEventDataImpersonationLevel + - log.data.ImpersonationLevel + to: log.eventDataImpersonationLevel - rename: from: - - log.wineventlog.event_data.FailureReason - to: log.winlogEventDataFailureReason + - log.wineventlog.data.FailureReason + to: log.eventDataFailureReason - rename: from: - - log.winlog.event_data.IpAddress - to: log.winlogEventDataIpAddress + - log.data.IpAddress + to: log.eventDataIpAddress - rename: from: - - log.winlog.event_data.IpPort - to: log.winlogEventDataIpPort + - log.data.IpPort + to: log.eventDataIpPort - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.KeyLength - to: log.winlogEventDataKeyLength + - log.data.KeyLength + to: log.eventDataKeyLength - rename: from: - - log.winlog.event_data.LmPackageName - to: log.winlogEventDataLmPackageName + - log.data.LmPackageName + to: log.eventDataLmPackageName - rename: from: - - log.winlog.event_data.LogonProcessName - to: log.winlogEventDataLogonProcessName + - log.data.LogonProcessName + to: log.eventDataLogonProcessName - rename: from: - - log.winlog.event_data.LogonType - to: log.winlogEventDataLogonType - + - log.data.LogonType + to: log.eventDataLogonType - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.ProcessId - to: log.winlogEventDataProcessId + - log.data.ProcessId + to: log.eventDataProcessId - rename: from: - - log.winlog.event_data.ProcessName - to: log.winlogEventDataProcessName + - log.data.ProcessName + to: log.eventDataProcessName - rename: from: - - log.winlog.event_data.RestrictedAdminMode - to: log.winlogEventDataRestrictedAdminMode + - log.data.RestrictedAdminMode + to: log.eventDataRestrictedAdminMode - rename: from: - - log.winlog.event_data.TargetDomainName + - log.data.TargetDomainName to: target.domain - rename: from: - - log.winlog.event_data.TargetLinkedLogonId - to: log.winlogEventDataTargetLinkedLogonId + - log.data.TargetLinkedLogonId + to: log.eventDataTargetLinkedLogonId - rename: from: - - log.winlog.event_data.Resource - to: log.winlogEventDataResource + - log.data.Resource + to: log.eventDataResource - rename: from: - - log.winlog.event_data.TargetLogonId - to: log.winlogEventDataTargetLogonId + - log.data.TargetLogonId + to: log.eventDataTargetLogonId - rename: from: - - log.winlog.event_data.TargetOutboundDomainName - to: log.winlogEventDataTargetOutboundDomainName + - log.data.TargetOutboundDomainName + to: log.eventDataTargetOutboundDomainName - rename: from: - - log.winlog.event_data.TargetOutboundUserName - to: log.winlogEventDataTargetOutboundUserName + - log.data.TargetOutboundUserName + to: log.eventDataTargetOutboundUserName - rename: from: - - log.winlog.event_data.TargetUserName + - log.data.TargetUserName to: target.user - rename: from: - - log.winlog.event_data.TargetUserSid - to: log.winlogEventDataTargetUserSid - - - rename: - from: - - log.winlog.event_data.TransmittedServices - to: log.winlogEventDataTransmittedServices + - log.data.TargetUserSid + to: log.eventDataTargetUserSid - rename: from: - - log.winlog.event_data.VirtualAccount - to: log.winlogEventDataVirtualAccount + - log.data.TransmittedServices + to: log.eventDataTransmittedServices - rename: from: - - log.winlog.event_data.WorkstationName - to: log.winlogEventDataWorkstationName + - log.data.VirtualAccount + to: log.eventDataVirtualAccount - rename: from: - - log.winlog.event_data.FailureReason - to: log.winlogEventDataFailureReason + - log.data.WorkstationName + to: log.eventDataWorkstationName - rename: from: - - log.winlog.event_data.AccessMask - to: log.winlogEeventDataAccessMask + - log.data.FailureReason + to: log.eventDataFailureReason - rename: from: - - log.winlog.event_data.Status - to: log.winlogEventDataStatus + - log.data.AccessMask + to: log.eventDataAccessMask - rename: from: - - log.winlog.opcode - to: log.winlogOpcode + - log.data.Status + to: log.eventDataStatus - rename: from: - - log.winlog.process.thread - to: log.winlogProcessThread + - log.process.thread + to: log.processThread - rename: from: - - log.winlog.task - to: log.winlogOpcode + - log.execution.ThreadID + to: log.processThreadId - rename: from: - - log.winlog.process.thread.id - to: log.winlogProcessThreadId + - log.process.pid + to: log.processPid - rename: from: - - log.winlog.process.pid - to: log.winlogProcessPid + - log.providername + to: log.providerName - rename: from: - - log.winlog.provider_name - to: log.winlogProviderName + - log.record_id + to: log.recordId - rename: from: - - log.winlog.record_id - to: log.winlogRecordId - - - rename: - from: - - log.winlog.task - to: log.winlogTask + - log.task + to: log.task - rename: from: @@ -362,13 +306,13 @@ pipeline: - rename: from: - - log.winlog.api - to: log.winlogApi + - log.api + to: log.api - rename: from: - - log.winlog.channel - to: log.winlogChannel + - log.channel + to: log.channel - rename: from: @@ -377,23 +321,18 @@ pipeline: - rename: from: - - log.winlog.activity_id + - log.correlation.ActivityID to: log.activityId - rename: from: - - log.winlog.event_data.LogonGuid + - log.data.LogonGuid to: log.logonGuid - - cast: - to: "[]string" - fields: - - log.origin.ips - - - cast: - to: "[]string" - fields: - - log.origin.macs + - rename: + from: + - log.execution.ProcessID + to: log.executionProcessID - cast: to: "int" @@ -407,8 +346,8 @@ pipeline: - log.providerGuid - log.activityId - log.logonGuid - - log.winlogEventDataSchema - - log.winlogProcessThread + - log.eventDataSchema + - log.processThread - trim: function: suffix @@ -417,8 +356,8 @@ pipeline: - log.providerGuid - log.activityId - log.logonGuid - - log.winlogEventDataSchema - - log.winlogProcessThread + - log.eventDataSchema + - log.processThread # Drop unnecessary events - drop: @@ -2293,49 +2232,49 @@ pipeline: params: key: log.failureReasonDescription value: 'The specified user account has expired.' - where: equals("log.winlogEventDataFailureReason", "%%2305") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2305") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'The password for the specified account has expired' - where: equals("log.winlogEventDataFailureReason", "%%2309") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2309") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'Account currently disabled' - where: equals("log.winlogEventDataFailureReason", "%%2310") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2310") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'Account logon time restriction violation' - where: equals("log.winlogEventDataFailureReason", "%%2311") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2311") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'User not allowed to logon at this computer' - where: equals("log.winlogEventDataFailureReason", "%%2312") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2312") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'Unknown user name or bad password' - where: equals("log.winlogEventDataFailureReason", "%%2313") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2313") && equals("log.eventCode", 4625) - add: function: 'string' params: key: log.failureReasonDescription value: 'An Error occurred during Logon' - where: equals("log.winlogEventDataFailureReason", "%%2304") && equals("log.eventCode", 4625) + where: equals("log.eventDataFailureReason", "%%2304") && equals("log.eventCode", 4625) # Decoding the "AccessMask" field when the event code is 4663 and adding the "accessType" and "accessDescription" field - add: @@ -2343,210 +2282,210 @@ pipeline: params: key: log.accessType value: 'read' - where: equals("log.winlogEeventDataAccessMask", "0x1") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x1") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to read the corresponding file data. For a directory object, the right to read the corresponding directory data.\n For a directory, the right to list the contents of the directory.\n For registry objects, this is, Query key value.' - where: equals("log.winlogEeventDataAccessMask", "0x1") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x1") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write' - where: equals("log.winlogEeventDataAccessMask", "0x2") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x2") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to write data to the file.\n For a directory object, the right to create a file in the directory.\n For registry objects, this is, Set key value.' - where: equals("log.winlogEeventDataAccessMask", "0x2") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x2") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'append' - where: equals("log.winlogEeventDataAccessMask", "0x4") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x4") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to append data to the file. (For local files, write operations will not overwrite existing data if this flag is specified without FILE_WRITE_DATA.)\n For a directory object, the right to create a subdirectory.\n For a named pipe, the right to create a pipe.' - where: equals("log.winlogEeventDataAccessMask", "0x4") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x4") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_extended_attributes' - where: equals("log.winlogEeventDataAccessMask", "0x8") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x8") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read extended file attributes.\n For registry objects, this is, Enumerate sub-keys.' - where: equals("log.winlogEeventDataAccessMask", "0x8") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x8") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_extended_attributes' - where: equals("log.winlogEeventDataAccessMask", "0x10") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x10") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to write extended file attributes.' - where: equals("log.winlogEeventDataAccessMask", "0x10") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x10") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'execute' - where: equals("log.winlogEeventDataAccessMask", "0x20") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x20") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a native code file, the right to execute the file. This access right given to scripts may cause the script to be executable, depending on the script interpreter.\n For a directory, the right to traverse the directory. By default, users are assigned the BYPASS_TRAVERSE_CHECKING privilege, which ignores the FILE_TRAVERSE access right.' - where: equals("log.winlogEeventDataAccessMask", "0x20") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x20") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'delete_child' - where: equals("log.winlogEeventDataAccessMask", "0x40") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x40") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a directory, the right to delete a directory and all the files it contains, including read-only files.' - where: equals("log.winlogEeventDataAccessMask", "0x40") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x40") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_attributes' - where: equals("log.winlogEeventDataAccessMask", "0x80") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x80") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read file attributes.' - where: equals("log.winlogEeventDataAccessMask", "0x80") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x80") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_attributes' - where: equals("log.winlogEeventDataAccessMask", "0x100") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x100") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to write file attributes.' - where: equals("log.winlogEeventDataAccessMask", "0x100") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x100") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'delete' - where: equals("log.winlogEeventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to delete the object.' - where: equals("log.winlogEeventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_control' - where: equals("log.winlogEeventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read information in the security descriptor object, without including the information in the system access control list (SACL).' - where: equals("log.winlogEeventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_dac' - where: equals("log.winlogEeventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to modify the discretionary access control list (DACL) in the security descriptor object.' - where: equals("log.winlogEeventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_owner' - where: equals("log.winlogEeventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to change the owner in the security descriptor object' - where: equals("log.winlogEeventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'synchronize' - where: equals("log.winlogEeventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state. Some object types do not support this access right.' - where: equals("log.winlogEeventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'access_sys_sec' - where: equals("log.winlogEeventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The ACCESS_SYS_SEC access right controls the ability to get or set the SACL in an security descriptor object.' - where: equals("log.winlogEeventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) + where: equals("log.eeventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) # Decoding the "eventStatus" field - add: @@ -2554,91 +2493,91 @@ pipeline: params: key: log.statusDescription value: 'Account locked out' - where: equals("log.winlogEventDataStatus", "0xC0000234") + where: equals("log.eventDataStatus", "0xC0000234") - add: function: 'string' params: key: log.statusDescription value: 'Account expired' - where: equals("log.winlogEventDataStatus", "0xC0000193") + where: equals("log.eventDataStatus", "0xC0000193") - add: function: 'string' params: key: log.statusDescription value: 'Clocks out of sync' - where: equals("log.winlogEventDataStatus", "0xC0000133") + where: equals("log.eventDataStatus", "0xC0000133") - add: function: 'string' params: key: log.statusDescription value: 'Password change required' - where: equals("log.winlogEventDataStatus", "0xC0000224") + where: equals("log.eventDataStatus", "0xC0000224") - add: function: 'string' params: key: log.statusDescription value: 'User does not have logon right' - where: equals("log.winlogEventDataStatus", "0xc000015b") + where: equals("log.eventDataStatus", "0xc000015b") - add: function: 'string' params: key: log.statusDescription value: 'Logon failure' - where: equals("log.winlogEventDataStatus", "0xc000006d") + where: equals("log.eventDataStatus", "0xc000006d") - add: function: 'string' params: key: log.statusDescription value: 'Account restriction' - where: equals("log.winlogEventDataStatus", "0xc000006e") + where: equals("log.eventDataStatus", "0xc000006e") - add: function: 'string' params: key: log.statusDescription value: 'An error occurred during logon' - where: equals("log.winlogEventDataStatus", "0xc00002ee") + where: equals("log.eventDataStatus", "0xc00002ee") - add: function: 'string' params: key: log.statusDescription value: 'Password expired' - where: equals("log.winlogEventDataStatus", "0xC0000071") + where: equals("log.eventDataStatus", "0xC0000071") - add: function: 'string' params: key: log.statusDescription value: 'Account disabled' - where: equals("log.winlogEventDataStatus", "0xC0000072") + where: equals("log.eventDataStatus", "0xC0000072") - add: function: 'string' params: key: log.statusDescription value: 'Authentication firewall prohibits logon' - where: equals("log.winlogEventDataStatus", "0xC0000413") + where: equals("log.eventDataStatus", "0xC0000413") - add: function: 'string' params: key: log.statusDescription value: 'Incorrect password' - where: equals("log.winlogEventDataStatus", "0xc000006a") + where: equals("log.eventDataStatus", "0xc000006a") - add: function: 'string' params: key: log.statusDescription value: 'Account does not exist' - where: equals("log.winlogEventDataStatus", "0xc0000064") + where: equals("log.eventDataStatus", "0xc0000064") # Decoding the "eventStatus" field when the event code is 4771 and adding the "statusDescription" - add: @@ -2646,330 +2585,330 @@ pipeline: params: key: log.statusDescription value: 'Customer database entry has expired' - where: equals("log.winlogEventDataStatus", "0x1") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x1") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'The server database entry has expired' - where: equals("log.winlogEventDataStatus", "0x2") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Requested protocol version not supported' - where: equals("log.winlogEventDataStatus", "0x3") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x3") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Customer key encrypted in old master key' - where: equals("log.winlogEventDataStatus", "0x4") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x4") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Server key encrypted with old master key' - where: equals("log.winlogEventDataStatus", "0x5") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x5") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Client not found in Kerberos database' - where: equals("log.winlogEventDataStatus", "0x6") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x6") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Server not found in Kerberos database' - where: equals("log.winlogEventDataStatus", "0x7") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x7") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Multiple principal entries in database' - where: equals("log.winlogEventDataStatus", "0x8") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x8") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'The client or server has a null key' - where: equals("log.winlogEventDataStatus", "0x9") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x9") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Ticket not eligible for postdating' - where: equals("log.winlogEventDataStatus", "0xA") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xA") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Requested start time is later than end time' - where: equals("log.winlogEventDataStatus", "0xB") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xB") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC policy rejects request' - where: equals("log.winlogEventDataStatus", "0xC") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xC") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC cannot accommodate requested option' - where: equals("log.winlogEventDataStatus", "0xD") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xD") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC has no support for encryption type' - where: equals("log.winlogEventDataStatus", "0xE") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xE") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC has no support for checksum type' - where: equals("log.winlogEventDataStatus", "0xF") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0xF") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC has no support for padata type' - where: equals("log.winlogEventDataStatus", "0x10") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x10") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'KDC has no support for transited type' - where: equals("log.winlogEventDataStatus", "0x11") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x11") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Clients credentials have been revoked' - where: equals("log.winlogEventDataStatus", "0x12") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x12") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Credentials for server have been revoked' - where: equals("log.winlogEventDataStatus", "0x13") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x13") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'TGT has been revoked' - where: equals("log.winlogEventDataStatus", "0x14") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x14") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Client not yet valid - try again later' - where: equals("log.winlogEventDataStatus", "0x15") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x15") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Server not yet valid - try again later' - where: equals("log.winlogEventDataStatus", "0x16") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x16") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Password has expired' - where: equals("log.winlogEventDataStatus", "0x17") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x17") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Pre-authentication information was invalid' - where: equals("log.winlogEventDataStatus", "0x18") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x18") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Additional pre-authentication required' - where: equals("log.winlogEventDataStatus", "0x19") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x19") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Integrity check on decrypted field failed' - where: equals("log.winlogEventDataStatus", "0x1F") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x1F") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Ticket expired' - where: equals("log.winlogEventDataStatus", "0x20") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x20") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Ticket not yet valid' - where: equals("log.winlogEventDataStatus", "0x21") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x21") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Request is a replay' - where: equals("log.winlogEventDataStatus", "0x22") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x22") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'The ticket is not for us' - where: equals("log.winlogEventDataStatus", "0x23") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x23") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Ticket and authenticator is not match' - where: equals("log.winlogEventDataStatus", "0x24") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x24") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Clock skew too great' - where: equals("log.winlogEventDataStatus", "0x25") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x25") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Incorrect net address' - where: equals("log.winlogEventDataStatus", "0x26") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x26") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Protocol version mismatch' - where: equals("log.winlogEventDataStatus", "0x27") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x27") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Invalid msg type' - where: equals("log.winlogEventDataStatus", "0x28") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x28") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Message stream modified' - where: equals("log.winlogEventDataStatus", "0x29") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x29") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Message out of order' - where: equals("log.winlogEventDataStatus", "0x2A") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2A") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Specified version of key is not available' - where: equals("log.winlogEventDataStatus", "0x2C") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2C") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Service key not available' - where: equals("log.winlogEventDataStatus", "0x2D") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2D") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Mutual authentication failed' - where: equals("log.winlogEventDataStatus", "0x2E") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2E") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Incorrect message direction' - where: equals("log.winlogEventDataStatus", "0x2F") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x2F") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Alternative authentication method required' - where: equals("log.winlogEventDataStatus", "0x30") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x30") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Incorrect sequence number in message' - where: equals("log.winlogEventDataStatus", "0x31") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x31") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Inappropriate type of checksum in message' - where: equals("log.winlogEventDataStatus", "0x32") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x32") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Generic error (description in e-text)' - where: equals("log.winlogEventDataStatus", "0x3C") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x3C") && equals("log.eventCode", 4771) - add: function: 'string' params: key: log.statusDescription value: 'Field is too long for this implementation' - where: equals("log.winlogEventDataStatus", "0x3D") && equals("log.eventCode", 4771) + where: equals("log.eventDataStatus", "0x3D") && equals("log.eventCode", 4771) - delete: fields: - log.agent - log.host - - log.winlog.computer_name - - log.winlog.event_data - - log.winlog.process + - log.computer_name + - log.data + - log.process - log.metadata - log.event - log.ecs From b2562e73140b88c01412c141b97caac2f78e0fc0 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 23 Feb 2026 09:13:02 -0600 Subject: [PATCH 118/240] feat(saml): enhance SAML registration with improved error handling and environment variable validation --- ...amlRelyingPartyRegistrationRepository.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java index 49c063cc6..ad5bf1574 100644 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java @@ -4,18 +4,21 @@ import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; import com.park.utmstack.repository.idp_provider.IdentityProviderConfigRepository; import com.park.utmstack.util.CipherUtil; +import com.park.utmstack.util.exceptions.ApiException; import com.park.utmstack.util.saml.PemUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +@Slf4j public class SamlRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository { private final Map registrations = new ConcurrentHashMap<>(); @@ -42,11 +45,15 @@ private void loadProviders(IdentityProviderConfigRepository jpaProviderRepositor } private RelyingPartyRegistration buildRelyingPartyRegistration(IdentityProviderConfig entity) { + try { + String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); + if (encryptionKey == null || encryptionKey.isBlank()) { + throw new IllegalStateException( + "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); + } - PrivateKey spKey = PemUtils.parsePrivateKey(CipherUtil.decrypt( - entity.getSpPrivateKeyPem(), - System.getenv(Constants.ENV_ENCRYPTION_KEY) - )); + String decryptedKey = CipherUtil.decrypt(entity.getSpPrivateKeyPem(), encryptionKey); + PrivateKey spKey = PemUtils.parsePrivateKey(decryptedKey); X509Certificate spCert = PemUtils.parseCertificate(entity.getSpCertificatePem()); return RelyingPartyRegistrations @@ -56,6 +63,10 @@ private RelyingPartyRegistration buildRelyingPartyRegistration(IdentityProviderC .assertionConsumerServiceLocation(entity.getSpAcsUrl()) .signingX509Credentials(c -> c.add(Saml2X509Credential.signing(spKey, spCert))) .build(); + } catch (Exception e) { + log.error("Failed to build SAML registration for provider: {}", entity.getName(), e); + throw new ApiException(String.format("Failed to build SAML registration for provider: %s", entity.getName()), HttpStatus.INTERNAL_SERVER_ERROR); } +} } \ No newline at end of file From bc21339c393ba30969e16508855a9f7ecf8e7f14 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 23 Feb 2026 09:16:51 -0600 Subject: [PATCH 119/240] feat(saml): improve SAML provider loading with enhanced error handling and logging --- ...amlRelyingPartyRegistrationRepository.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java index ad5bf1574..4c5744056 100644 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java @@ -15,6 +15,7 @@ import java.security.PrivateKey; import java.security.cert.X509Certificate; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -38,11 +39,29 @@ public void reloadProviders(IdentityProviderConfigRepository jpaProviderReposito } private void loadProviders(IdentityProviderConfigRepository jpaProviderRepository) { - jpaProviderRepository.findAllByActiveTrue().forEach(entity -> { - RelyingPartyRegistration registration = buildRelyingPartyRegistration(entity); - registrations.put(entity.getProviderType().name().toLowerCase(), registration); + try { + List activeProviders = jpaProviderRepository.findAllByActiveTrue(); + + if (activeProviders.isEmpty()) { + return; + } + + activeProviders.forEach(entity -> { + try { + RelyingPartyRegistration registration = buildRelyingPartyRegistration(entity); + registrations.put(entity.getProviderType().name().toLowerCase(), registration); + log.info("Loaded SAML provider: {} (type: {})", entity.getName(), entity.getProviderType()); + } catch (Exception e) { + log.error("Failed to load SAML provider: {}", entity.getName(), e); + } }); + + log.info("Successfully loaded {} SAML provider(s)", registrations.size()); + } catch (Exception e) { + log.error("Failed to load SAML providers: {}", e.getMessage(), e); + throw new ApiException(String.format("Failed to load SAML providers: %s", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); } +} private RelyingPartyRegistration buildRelyingPartyRegistration(IdentityProviderConfig entity) { try { From 36231237adcbebc83517d47473fddbd5ee59a745 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 23 Feb 2026 09:32:08 -0600 Subject: [PATCH 120/240] feat(saml): enhance SAML provider loading with improved error handling and logging --- ...amlRelyingPartyRegistrationRepository.java | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java index 4c5744056..d1dd4b13e 100644 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java @@ -39,53 +39,53 @@ public void reloadProviders(IdentityProviderConfigRepository jpaProviderReposito } private void loadProviders(IdentityProviderConfigRepository jpaProviderRepository) { - try { - List activeProviders = jpaProviderRepository.findAllByActiveTrue(); + try { + List activeProviders = jpaProviderRepository.findAllByActiveTrue(); - if (activeProviders.isEmpty()) { - return; - } - - activeProviders.forEach(entity -> { - try { - RelyingPartyRegistration registration = buildRelyingPartyRegistration(entity); - registrations.put(entity.getProviderType().name().toLowerCase(), registration); - log.info("Loaded SAML provider: {} (type: {})", entity.getName(), entity.getProviderType()); - } catch (Exception e) { - log.error("Failed to load SAML provider: {}", entity.getName(), e); + if (activeProviders.isEmpty()) { + return; } - }); - log.info("Successfully loaded {} SAML provider(s)", registrations.size()); - } catch (Exception e) { - log.error("Failed to load SAML providers: {}", e.getMessage(), e); - throw new ApiException(String.format("Failed to load SAML providers: %s", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); + activeProviders.forEach(entity -> { + try { + RelyingPartyRegistration registration = buildRelyingPartyRegistration(entity); + registrations.put(entity.getProviderType().name().toLowerCase(), registration); + log.info("Loaded SAML provider: {} (type: {})", entity.getName(), entity.getProviderType()); + } catch (Exception e) { + log.error("Failed to load SAML provider: {}", entity.getName(), e); + } + }); + + log.info("Successfully loaded {} SAML provider(s)", registrations.size()); + } catch (Exception e) { + log.error("Failed to load SAML providers: {}", e.getMessage(), e); + throw new ApiException(String.format("Failed to load SAML providers: %s", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); + } } -} private RelyingPartyRegistration buildRelyingPartyRegistration(IdentityProviderConfig entity) { - try { - String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); - if (encryptionKey == null || encryptionKey.isBlank()) { - throw new IllegalStateException( - "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); - } + try { + String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); + if (encryptionKey == null || encryptionKey.isBlank()) { + throw new IllegalStateException( + "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); + } - String decryptedKey = CipherUtil.decrypt(entity.getSpPrivateKeyPem(), encryptionKey); - PrivateKey spKey = PemUtils.parsePrivateKey(decryptedKey); - X509Certificate spCert = PemUtils.parseCertificate(entity.getSpCertificatePem()); + String decryptedKey = CipherUtil.decrypt(entity.getSpPrivateKeyPem(), encryptionKey); + PrivateKey spKey = PemUtils.parsePrivateKey(decryptedKey); + X509Certificate spCert = PemUtils.parseCertificate(entity.getSpCertificatePem()); - return RelyingPartyRegistrations - .fromMetadataLocation(entity.getMetadataUrl()) - .registrationId(entity.getName()) - .entityId(entity.getSpEntityId()) - .assertionConsumerServiceLocation(entity.getSpAcsUrl()) - .signingX509Credentials(c -> c.add(Saml2X509Credential.signing(spKey, spCert))) - .build(); - } catch (Exception e) { - log.error("Failed to build SAML registration for provider: {}", entity.getName(), e); - throw new ApiException(String.format("Failed to build SAML registration for provider: %s", entity.getName()), HttpStatus.INTERNAL_SERVER_ERROR); + return RelyingPartyRegistrations + .fromMetadataLocation(entity.getMetadataUrl()) + .registrationId(entity.getName()) + .entityId(entity.getSpEntityId()) + .assertionConsumerServiceLocation(entity.getSpAcsUrl()) + .signingX509Credentials(c -> c.add(Saml2X509Credential.signing(spKey, spCert))) + .build(); + } catch (Exception e) { + log.error("Failed to build SAML registration for provider: {}", entity.getName(), e); + throw new ApiException(String.format("Failed to build SAML registration for provider: %s", entity.getName()), HttpStatus.INTERNAL_SERVER_ERROR); + } } -} } \ No newline at end of file From b99c596ae11e5d167dbb16d3598c568ac33fced6 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 23 Feb 2026 10:35:01 -0600 Subject: [PATCH 121/240] feat(correlation): add updates for winevent correlation rules --- ...3002_update_winevent_correlation_rules.xml | 35 + .../data/20260223/utm_correlation_rules.sql | 2314 +++++++++++++++++ .../20260223/utm_group_rules_data_type.sql | 221 ++ 3 files changed, 2570 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260223002_update_winevent_correlation_rules.xml create mode 100644 backend/src/main/resources/config/liquibase/data/20260223/utm_correlation_rules.sql create mode 100644 backend/src/main/resources/config/liquibase/data/20260223/utm_group_rules_data_type.sql diff --git a/backend/src/main/resources/config/liquibase/changelog/20260223002_update_winevent_correlation_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260223002_update_winevent_correlation_rules.xml new file mode 100644 index 000000000..77917533f --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260223002_update_winevent_correlation_rules.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/data/20260223/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260223/utm_correlation_rules.sql new file mode 100644 index 000000000..287c15d2f --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260223/utm_correlation_rules.sql @@ -0,0 +1,2314 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (879, 'Windows: Signed Proxy Execution via MS Work Folders', 1, 2, 3, 'Defense Evasion', 'T1218 - System Binary Proxy Execution', 'Identifies the use of Windows Work Folders to execute a potentially masqueraded control.exe file in the current working directory. Misuse of Windows Work Folders could indicate malicious activity.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1218/"]', 'contains("log.eventDataProcessName", "control.exe") && contains("log.eventDataParentProcessName", "workfolders.exe") && !regexMatch("log.eventDataProcessName", "(:\\Windows\\(System32|SysWOW64)\\control.exe)")', '2026-02-23 16:15:10.139988', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (880, 'Windows: Wireless Credential Dumping using Netsh Command', 3, 3, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies attempts to dump Wireless saved access keys in clear text using the Windows built-in utility Netsh.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'contains("log.message", "wlan") && regexMatch("log.message", "(key(.+)clear)") && contains("log.eventDataProcessName", "netsh.exe")', '2026-02-23 16:15:11.418994', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (881, 'Windows: Unusual Process Network Connection', 3, 3, 2, 'Defense Evasion', 'Trusted Developer Utilities Proxy Execution', 'Identifies network activity from unexpected system applications. This may indicate adversarial activity as these applications are often leveraged by adversaries to execute code and evade detection.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1127/"]', 'regexMatch("log.eventDataProcessName", "(Microsoft.Workflow.Compiler.exe|bginfo.exe|cdb.exe|cmstp.exe|csi.exe|dnx.exe|fsi.exe|ieexec.exe|iexpress.exe|odbcconf.exe|rcsi.exe|xwizard.exe)")', '2026-02-23 16:15:12.474670', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataProcessID","operator":"filter_term","value":"{{log.eventDataProcessID}}"}],"or":null,"within":"now-5m","count":3}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (882, 'Windows: Unusual Child Process of dns.exe', 1, 3, 2, 'Initial Access', 'T1133 - External Remote Services', 'Identifies an unexpected process spawning from dns.exe, the process responsible for Windows DNS server services, which may indicate activity related to remote code execution or other forms of exploitation.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1133/"]', 'contains("log.eventDataProcessName", "dns.exe") && !contains("log.eventDataParentProcessName", "conhost.exe")', '2026-02-23 16:15:13.474465', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (883, 'Windows: System Shells via Services', 1, 3, 2, 'Persistence', 'T1543.003 - Create or Modify System Process: Windows Service', 'Windows services typically run as SYSTEM and can be used as a privilege escalation opportunity. Malware or penetration testers may run a shell as a service to gain SYSTEM permissions.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1543/003/"]', e'regexMatch("log.eventDataProcessName", "(cmd.exe|powershell.exe|pwsh.exe|powershell_ise.exe)") && + contains("log.eventDataParentProcessName", "services.exe") && + !(regexMatch("log.message", "(NVDisplay.ContainerLocalSystem)")) +', '2026-02-23 16:15:14.566192', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (884, 'Windows: Symbolic Link to Shadow Copy Created', 1, 3, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Detects creation of a symbolic link to a volume shadow copy. Adversaries may use this technique to access and exfiltrate sensitive data such as NTDS.dit or SAM database from shadow copies.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'regexMatch("log.eventDataProcessName", "(cmd.exe|powershell.exe|pwsh.exe|powershell_ise.exe)") && regexMatch("log.message", "(?i)(mklink|New-Item.*SymbolicLink)") && contains("log.message", "HarddiskVolumeShadowCopy")', '2026-02-23 16:15:15.645080', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (885, 'Windows: Suspicious RDP ActiveX Client Loaded', 1, 3, 2, 'Lateral Movement', 'T1021 - Remote Services', 'Identifies suspicious Image Loading of the Remote Desktop Services ActiveX Client (mstscax), this may indicate the presence of RDP lateral movement capability.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/"]', e'!(regexMatch("log.eventDataProcessName", "(C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\mstsc\\\\.exe|C:\\\\\\\\Windows\\\\\\\\SysWOW64\\\\\\\\mstsc\\\\.exe)")) && +regexMatch("log.eventDataProcessName", "(C:\\\\\\\\Windows\\\\\\\\|C:\\\\\\\\Users\\\\\\\\Public\\\\\\\\|C:\\\\\\\\Users\\\\\\\\Default\\\\\\\\|C:\\\\\\\\Intel\\\\\\\\|C:\\\\\\\\PerfLogs\\\\\\\\|C:\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Device\\\\\\\\Mup\\\\\\\\|\\\\\\\\\\\\\\\\)") && +contains("log.message", "mstscax.dll") +', '2026-02-23 16:15:16.831651', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (886, 'Windows: Suspicious Process Execution via Renamed PsExec Executable', 1, 3, 2, 'Execution', 'T1569 - System Services', 'Identifies suspicious psexec activity which is executing from the psexec service that has been renamed, possibly to vade detection.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1569/"]', 'equals("log.eventDataEventType", "start") && (contains("log.eventDataProcessName", "PSEXESVC.exe") || contains("log.eventDataOriginalFileName", "psexesvc.exe"))', '2026-02-23 16:15:17.881604', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (887, 'Windows: Suspicious Execution via Scheduled Task', 1, 3, 2, 'Persistence', 'T1053.005 - Scheduled Task', 'Identifies execution of a suspicious program via scheduled tasks by looking at process lineage and command line usage.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1053/005/"]', e'equals("log.eventDataEventType", "start") && contains("log.eventDataProcessParentName", "svchost.exe") && contains("log.eventDataProcessParentArgs", "Schedule") && +regexMatch("log.eventDataOriginalFileName", "(cscript.exe|wscript.exe|PowerShell.EXE|Cmd.Exe|MSHTA.EXE|RUNDLL32.EXE|REGSVR32.EXE|MSBuild.exe|InstallUtil.exe|RegAsm.exe|RegSvcs.exe|msxsl.exe|CONTROL.EXE|EXPLORER.EXE|Microsoft.Workflow.Compiler.exe|msiexec.exe)") && +regexMatch("log.eventDataProcessArgs", "(C:\\\\\\\\Users\\\\\\\\|C:\\\\\\\\ProgramData\\\\\\\\|C:\\\\\\\\Windows\\\\\\\\Temp\\\\\\\\|C:\\\\\\\\Windows\\\\\\\\Tasks\\\\\\\\|C:\\\\\\\\PerfLogs\\\\\\\\|C:\\\\\\\\Intel\\\\\\\\|C:\\\\\\\\Windows\\\\\\\\Debug\\\\\\\\|C:\\\\\\\\HP\\\\\\\\)") && +!regexMatch("log.eventDataProcessName", "(cmd.exe|cscript.exe|powershell.exe|msiexec.exe)") && +!regexMatch("log.eventDataProcessArgs", "(:\\\\\\\\(.+).bat|:\\\\\\\\Windows\\\\\\\\system32\\\\\\\\calluxxprovider.vbs|-File|-PSConsoleFile)") && +!regexMatch("log.eventDataUserId", "(S-1-5-18)") && !regexMatch("log.eventDataWorkingDirectory", "(:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\)") +', '2026-02-23 16:15:18.952423', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (888, 'Windows: Suspicious PowerShell Engine ImageLoad', 1, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', 'Identifies the PowerShell engine being invoked by unexpected processes. Rather than executing PowerShell functionality with powershell.exe, some attackers do this to operate more stealthily.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1059/"]', e'!(oneOf("log.eventDataProcessName", ["Altaro.SubAgent.exe", "AppV_Manage.exe", "azureadconnect.exe", "CcmExec.exe", "configsyncrun.exe", "choco.exe", "ctxappvservice.exe", "DVLS.Console.exe", "edgetransport.exe", "exsetup.exe", "forefrontactivedirectoryconnector.exe", "InstallUtil.exe", "JenkinsOnDesktop.exe", "Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe", "mmc.exe", "mscorsvw.exe", "msexchangedelivery.exe", "msexchangefrontendtransport.exe", "msexchangehmworker.exe", "msexchangesubmission.exe", "msiexec.exe", "MsiExec.exe", "noderunner.exe", "NServiceBus.Host.exe", "NServiceBus.Host32.exe", "NServiceBus.Hosting.Azure.HostProcess.exe", "OuiGui.WPF.exe", "powershell.exe", "powershell_ise.exe", "pwsh.exe", "SCCMCliCtrWPF.exe", "ScriptEditor.exe", "ScriptRunner.exe", "sdiagnhost.exe", "servermanager.exe", "setup100.exe", "ServiceHub.VSDetouredHost.exe", "SPCAF.Client.exe", "SPCAF.SettingsEditor.exe", "SQLPS.exe", "telemetryservice.exe", "UMWokerProcess.exe", "w3wp.exe", "wsmprovhost.exe"])) && +!(regexMatch("log.eventDataProcessName", "(C:\\\\Windows\\\\System32\\\\RemoteFXvGPUDisablement.exe|C:\\\\Windows\\\\System32\\\\sdiagnhost.exe|C:\\\\Program Files( \\\\(x86\\\\))?\\\\(.+)\\\\.exe)")) && +oneOf("log.message", ["System.Management.Automation.ni.dll", "System.Management.Automation.dll"]) +', '2026-02-23 16:15:19.969203', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (889, 'Windows: Suspicious Process Access via Direct System Call', 1, 3, 2, 'Defense Evasion', 'T1055 - Process Injection', 'Identifies suspicious process access events from an unknown memory region. Endpoint security solutions usually hook userland Windows APIs in order to decide if the code that is being executed is malicious or not. It''s possible to bypass hooked functions by writing malicious functions that call syscalls directly.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1055/"]', e'equals("log.eventCode", 10) && !(regexMatch("log.eventDataCallTrace", "(:\\\\WINDOWS\\\\SYSTEM32\\\\ntdll.dll|:\\\\WINDOWS\\\\SysWOW64\\\\ntdll.dll|:\\\\Windows\\\\System32\\\\wow64cpu.dll|:\\\\WINDOWS\\\\System32\\\\wow64win.dll|:\\\\Windows\\\\System32\\\\win32u.dll)")) && +!(regexMatch("log.eventDataTargetImage", "(:\\\\WINDOWS\\\\system32\\\\lsass.exe|:\\\\Program Files (x86)\\\\Malwarebytes Anti-Exploit\\\\mbae-svc.exe|:\\\\Program Files\\\\Cisco\\\\AMP\\\\(.+)\\\\sfc.exe|:\\\\Program Files (x86)\\\\Microsoft\\\\EdgeWebView\\\\Application\\\\(.+)\\\\msedgewebview2.exe|:\\\\Program Files\\\\Adobe\\\\Acrobat DC\\\\Acrobat\\\\(.+)\\\\AcroCEF.exe)")) && +!(regexMatch("log.eventDataProcessName", "(:\\\\Program Files\\\\Adobe\\\\Acrobat DC\\\\Acrobat\\\\Acrobat.exe|:\\\\Program Files (x86)\\\\World of Warcraft\\\\_classic_\\\\WowClassic.exe)")) +', '2026-02-23 16:15:20.977194', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (890, 'Windows: Suspicious PDF Reader Child Process', 1, 1, 2, 'Execution', 'T1204 - User Execution', 'Identifies suspicious child processes of PDF reader applications. These child processes are often launched via exploitation of PDF applications or social engineering.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1204/"]', 'oneOf("log.eventDataParentProcessName", ["AcroRd32.exe", "Acrobat.exe", "FoxitPhantomPDF.exe", "FoxitReader.exe"]) && oneOf("log.eventDataProcessName", ["arp.exe", "dsquery.exe", "dsget.exe", "gpresult.exe", "hostname.exe", "ipconfig.exe", "nbtstat.exe", "net.exe", "net1.exe", "netsh.exe", "netstat.exe", "nltest.exe", "ping.exe", "qprocess.exe", "quser.exe", "qwinsta.exe", "reg.exe", "sc.exe", "systeminfo.exe", "tasklist.exe", "tracert.exe", "whoami.exe", "bginfo.exe", "cdb.exe", "cmstp.exe", "csi.exe", "dnx.exe", "fsi.exe", "ieexec.exe", "iexpress.exe", "installutil.exe", "Microsoft.Workflow.Compiler.exe", "msbuild.exe", "mshta.exe", "msxsl.exe", "odbcconf.exe", "rcsi.exe", "regsvr32.exe", "xwizard.exe", "atbroker.exe", "forfiles.exe", "schtasks.exe", "regasm.exe", "regsvcs.exe", "cmd.exe", "cscript.exe", "powershell.exe", "pwsh.exe", "wmic.exe", "wscript.exe", "bitsadmin.exe", "certutil.exe", "ftp.exe"])', '2026-02-23 16:15:22.100610', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (891, 'Windows: Suspicious MS Outlook Child Process', 1, 3, 2, 'Initial Access', 'T1566.001 - Phishing: Spearphishing Attachment', 'Identifies suspicious child processes of Microsoft Outlook. These child processes are often associated with spear phishing activity.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1566/001/"]', 'oneOf("log.eventDataProcessName", ["Microsoft.Workflow.Compiler.exe", "arp.exe", "atbroker.exe", "bginfo.exe", "bitsadmin.exe", "cdb.exe", "certutil.exe", "cmd.exe", "cmstp.exe", "cscript.exe", "csi.exe", "dnx.exe", "dsget.exe", "dsquery.exe", "forfiles.exe", "fsi.exe", "ftp.exe", "gpresult.exe", "hostname.exe", "ieexec.exe", "iexpress.exe", "installutil.exe", "ipconfig.exe", "mshta.exe", "msxsl.exe", "nbtstat.exe", "net.exe", "net1.exe", "netsh.exe", "netstat.exe", "nltest.exe", "odbcconf.exe", "ping.exe", "powershell.exe", "pwsh.exe", "qprocess.exe", "quser.exe", "qwinsta.exe", "rcsi.exe", "reg.exe", "regasm.exe", "regsvcs.exe", "regsvr32.exe", "sc.exe", "schtasks.exe", "systeminfo.exe", "tasklist.exe", "tracert.exe", "whoami.exe", "wmic.exe", "wscript.exe", "xwizard.exe"]) && contains("log.eventDataParentProcessName", "outlook.exe")', '2026-02-23 16:15:23.157906', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (892, 'Windows: Suspicious MS Office Child Process', 1, 2, 3, 'Initial Access', 'T1566.001 - Phishing: Spearphishing Attachment', 'Identifies suspicious child processes of frequently targeted Microsoft Office applications (Word, PowerPoint, Excel). These child processes are often launched during exploitation of Office applications or from documents with malicious macros.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1566/001/"]', 'oneOf("log.eventDataProcessName", ["Microsoft.Workflow.Compiler.exe", "arp.exe", "atbroker.exe", "bginfo.exe", "bitsadmin.exe", "cdb.exe", "certutil.exe", "cmd.exe", "cmstp.exe", "control.exe", "cscript.exe", "csi.exe", "dnx.exe", "dsget.exe", "dsquery.exe", "forfiles.exe", "fsi.exe", "ftp.exe", "gpresult.exe", "hostname.exe", "ieexec.exe", "iexpress.exe", "installutil.exe", "ipconfig.exe", "mshta.exe", "msxsl.exe", "nbtstat.exe", "net.exe", "net1.exe", "netsh.exe", "netstat.exe", "nltest.exe", "odbcconf.exe", "ping.exe", "powershell.exe", "pwsh.exe", "qprocess.exe", "quser.exe", "qwinsta.exe", "rcsi.exe", "reg.exe", "regasm.exe", "regsvcs.exe", "regsvr32.exe", "sc.exe", "schtasks.exe", "systeminfo.exe", "tasklist.exe", "tracert.exe", "whoami.exe", "wmic.exe", "wscript.exe", "xwizard.exe", "explorer.exe", "rundll32.exe", "hh.exe", "msdt.exe"]) && oneOf("log.eventDataParentProcessName", ["eqnedt32.exe", "excel.exe", "fltldr.exe", "msaccess.exe", "mspub.exe", "powerpnt.exe", "winword.exe", "outlook.exe"])', '2026-02-23 16:15:24.176084', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (893, 'Windows: Microsoft Exchange Worker Spawning Suspicious Processes', 2, 3, 2, 'Initial Access', 'T1190 - Exploit Public-Facing Application', 'Identifies suspicious processes being spawned by the Microsoft Exchange Server worker process (w3wp). This activity may indicate exploitation activity or access to an existing web shell backdoor.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1190/"]', 'contains("log.eventDataParentProcessName", "w3wp.exe") && regexMatch("log.message", "(MSExchange(.+)AppPool)") && regexMatch("log.eventDataProcessName", "(md.exe|powershell.exe|pwsh.dll|powershell_ise.exe)")', '2026-02-23 16:15:25.158589', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (894, 'Windows: Microsoft Exchange Worker Spawning Suspicious Processes', 2, 3, 2, 'Initial Access', 'T1190 - Exploit Public-Facing Application', 'Identifies suspicious processes being spawned by the Microsoft Exchange Server worker process (w3wp). This activity may indicate exploitation activity or access to an existing web shell backdoor.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1190/"]', 'regexMatch("log.eventDataParentProcessName", "(UMService.exe|UMWorkerProcess.exe)") && !(regexMatch("log.eventDataProcessName", "(:\\Windows\\System32\\werfault.exe|:\\Windows\\System32\\wermgr.exe|:\\Program Files\\Microsoft\\Exchange Server\\V(.+)\\Bin\\UMWorkerProcess.exe|D:\\Exchange 2016\\Bin\\UMWorkerProcess.exe|E:\\ExchangeServer\\Bin\\UMWorkerProcess.exe)"))', '2026-02-23 16:15:26.215882', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (895, 'Windows: Suspicious Managed Code Hosting Process', 3, 3, 2, 'Defense Evasion', 'T1055 - Process Injection', 'Identifies a suspicious managed code hosting process which could indicate code injection or other form of suspicious code execution.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1055/"]', 'regexMatch("log.eventDataProcessName", "(wscript.exe|cscript.exe|mshta.exe|wmic.exe|regsvr32.exe|svchost.exe|dllhost.exe|cmstp.exe)")', '2026-02-23 16:15:27.311555', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataProcessName","operator":"filter_term","value":"{{log.eventDataProcessName}}"},{"field":"log.eventdataProcessID","operator":"filter_term","value":"{{log.eventdataProcessID}}"}],"or":null,"within":"now-5m","count":3}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (896, 'Windows: Potential LSASS Memory Dump via PssCaptureSnapShot', 3, 3, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies suspicious access to an LSASS handle via PssCaptureSnapShot where two successive process accesses are performed by the same process and target two different instances of LSASS. This may indicate an attempt to evade detection and dump LSASS memory for credential access.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'equals("log.eventId", "10") && regexMatch("log.eventDataTargetImage", "([Cc]:\\Windows\\[Ss]ystem32\\lsass.exe)")', '2026-02-23 16:15:28.444660', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (897, 'Windows: Potential Credential Access via LSASS Memory Dump', 3, 3, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies suspicious access to LSASS handle from a call trace pointing to DBGHelp.dll or DBGCore.dll, which both export the MiniDumpWriteDump method that can be used to dump LSASS memory content in preparation for credential access.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'equals("log.eventId", "10") && regexMatch("log.eventDataTargetImage", "(C:\\\\WINDOWS\\\\system32\\\\lsass.exe)") && regexMatch("log.eventDataCallTrace", "(dbghelp|dbgcore)") && !regexMatch("log.eventDataProcessName", "(C:\\\\Windows\\\\System32\\\\WerFault.exe|C:\\\\Windows\\\\System32\\\\WerFaultSecure.exe)")', '2026-02-23 16:15:29.628201', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (898, 'Windows: Remote Computer Account DnsHostName Update', 3, 3, 2, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', 'Identifies the remote update to a computer account''s DnsHostName attribute. If the new value set is a valid domain controller DNS hostname and the subject computer name is not a domain controller, then it''s highly likely a preparation step to exploit CVE-2022-26923 in an attempt to elevate privileges from a standard domain user to domain admin privileges.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1068/"]', 'equals("log.action", "logged-in") && regexMatch("actionResult", "success") && !contains("log.userName", "ANONYMOUS LOGON") && !contains("log.eventDataSubjectUserName", "ANONYMOUS LOGON") && startsWith("log.eventDataSubjectUserName", "$") && !contains("log.userDomain", "NT AUTHORITY")', '2026-02-23 16:15:30.803228', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (899, 'Windows: Potential Credential Access via Renamed COM+ Services DLL', 3, 3, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies suspicious renamed COMSVCS.DLL Image Load, which exports the MiniDump function that can be used to dump a process memory. This may indicate an attempt to dump LSASS memory while bypassing command-line based detection in preparation for credential access.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/001/"]', 'contains("log.eventCategory", "process") && contains("log.eventProcessName", "rundll32.exe") && regexMatch("log.eventDataset", "(windows.sysmon_operational)") && equals("log.eventId", "7") && !equals("log.fileName", "COMSVCS.DLL") && (regexMatch("log.filePeOriginalFileName", "(COMSVCS.DLL)") || regexMatch("log.filePeImphash", "(EADBCCBB324829ACB5F2BBE87E5549A8)"))', '2026-02-23 16:15:31.977408', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.processEntityId","operator":"filter_term","value":"{{log.processEntityId}}"},{"field":"log.eventCategory","operator":"filter_term","value":"process"}],"or":null,"within":"now-5m","count":3}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (900, 'Windows: Service Control Spawned via Script Interpreter', 2, 3, 2, 'Lateral Movement', 'T1021 - Remote Services', 'Identifies Service Control (sc.exe) spawning from script interpreter processes to create, modify, or start services. This could be indicative of adversary lateral movement but will be noisy if commonly done by admins.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/"]', 'oneOf("log.eventDataParentProcessName", ["cmd.exe", "wscript.exe", "rundll32.exe", "regsvr32.exe", "wmic.exe", "mshta.exe", "powershell.exe", "pwsh.exe"]) && oneOf("log.message", ["config", "create", "start", "delete", "stop", "pause"]) && !equals("log.eventDataSubjectUserName", "S-1-5-18") && contains("log.eventDataProcessName", "sc.exe")', '2026-02-23 16:15:33.127722', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (901, 'Windows: Sensitive Privilege SeEnableDelegationPrivilege assigned to a User', 3, 3, 1, 'Credential Access', 'T1212 - Exploitation for Credential Access', 'Identifies the assignment of the SeEnableDelegationPrivilege sensitive user right to a user. The SeEnableDelegationPrivilege user right enables computer and user accounts to be trusted for delegation. Attackers can abuse this right to compromise Active Directory accounts and elevate their privileges.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1212/"]', 'regexMatch("action", "([Aa]uthorization [Pp]olicy [Cc]hange)") && equals("log.eventCode", 4704) && contains("log.eventDataPrivilegeList", "SeEnableDelegationPrivilege")', '2026-02-23 16:15:34.306254', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (902, 'Windows: Security Software Discovery using WMIC', 3, 2, 1, 'Discovery', 'T1518.001 - Software Discovery: Security Software Discovery', 'Identifies the use of Windows Management Instrumentation Command (WMIC) to discover certain System Security Settings such as AntiVirus or Host Firewall details.', '["https://attack.mitre.org/tactics/TA0007/","https://attack.mitre.org/techniques/T1518/001/"]', 'regexMatch("log.message", "(namespace:\\\\root\\SecurityCenter2)") && contains("log.message", "Get") && contains("log.eventDataProcessName", "wmic.exe")', '2026-02-23 16:15:35.458891', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (903, 'Windows: Potential Secure File Deletion via SDelete Utility', 3, 2, 2, 'Defense Evasion', 'T1070.004 - Indicator Removal: File Deletion', 'Detects file name patterns generated by the use of Sysinternals SDelete utility to securely delete a file via multiple file overwrite and rename operations.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1070/004/"]', 'equals("log.eventDataEventType", "change") && contains("log.eventDataFileName", "AAA.AAA")', '2026-02-23 16:15:36.546981', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (979, 'Windows: SeDebugPrivilege Enabled by a Suspicious Process', 1, 3, 2, 'Privilege Escalation', 'T1134 - Access Token Manipulation', 'Identifies the creation of a process running as SYSTEM and impersonating a Windows core binary privileges. Adversaries may create a new process with a different token to escalate privileges and bypass access controls.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1134/"]', 'regexMatch("log.action", "(Token Right Adjusted Events)") && regexMatch("log.eventProvider", "(Microsoft-Windows-Security-Auditing)") && regexMatch("log.eventDataEnabledPrivilegeList", "(SeDebugPrivilege)") && !(oneOf("log.eventDataSubjectUserSid", ["S-1-5-18", "S-1-5-19", "S-1-5-20"])) && !(regexMatch("log.eventDataProcessName", "(:\\Windows\\System32\\msiexec.exe|:\\Windows\\SysWOW64\\msiexec.exe|:\\Windows\\System32\\lsass.exe|:\\Windows\\WinSxS\\|:\\Program Files\\|:\\Program Files (x86)\\|:\\Windows\\System32\\MRT.exe|:\\Windows\\System32\\cleanmgr.exe|:\\Windows\\System32\\taskhostw.exe|:\\Windows\\System32\\mmc.exe|:\\Users\\(.+)\\AppData\\Local\\Temp\\(.+)-(.+)\\DismHost.exe|:\\Windows\\System32\\auditpol.exe|:\\Windows\\System32\\wbem\\WmiPrvSe.exe|:\\Windows\\SysWOW64\\wbem\\WmiPrvSe.exe)"))', '2026-02-23 16:17:01.932948', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (980, 'Windows: Suspicious WMIC XSL Script Execution', 2, 3, 2, 'Defense Evasion', 'T1220 - XSL Script Processing', 'Identifies WMIC allowlist bypass techniques by alerting on suspicious execution of scripts. When WMIC loads scripting libraries it may be indicative of an allowlist bypass.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1220/"]', 'regexMatch("log.message", "(?i)wmic.*format") && !contains("log.message", "/format:table") && regexMatch("log.message", "(?i)(jscript\\.dll|vbscript\\.dll)")', '2026-02-23 16:17:03.063606', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (981, 'Windows: Microsoft Exchange Server UM Writing Suspicious Files', 2, 3, 2, 'Initial Access', 'T1190 - Exploit Public-Facing Application', 'Identifies suspicious files being written by the Microsoft Exchange Server Unified Messaging (UM) service. This activity has been observed exploiting CVE-2021-26858.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1190/"]', 'regexMatch("log.eventDataProcessName", "(UMWorkerProcess.exe|umservice.exe)") && regexMatch("log.message", "(php|jsp|js|aspx|asmx|asax|cfm|shtml)") && regexMatch("log.message", "(:\\\\inetpub\\\\wwwroot\\\\aspnet_client\\\\|:\\\\(.+)\\\\Microsoft\\\\Exchange Server(.+)\\\\FrontEnd\\\\HttpProxy\\\\owa\\\\auth\\\\)") && !regexMatch("log.message", "(:\\\\(.+)\\\\Microsoft\\\\Exchange Server(.+)\\\\FrontEnd\\\\HttpProxy\\\\(owa|ecp)\\\\auth\\\\version\\\\)")', '2026-02-23 16:17:04.093307', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (982, 'Windows: Suspicious WMI Image Load from MS Office', 1, 2, 3, 'Execution', 'T1047 - Windows Management Instrumentation', 'Identifies a suspicious image load (wmiutils.dll) from Microsoft Office processes. This behavior may indicate adversarial activity where child processes are spawned via Windows Management Instrumentation (WMI). This technique can be used to execute code and evade traditional parent/child processes spawned from Microsoft Office products.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1047/"]', 'regexMatch("log.eventDataProcessName", "(WINWORD.EXE|EXCEL.EXE|POWERPNT.EXE|MSPUB.EXE|MSACCESS.EXE)") && contains("log.message", "wmiutils.dll")', '2026-02-23 16:17:05.147941', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (983, 'Windows: Suspicious Image Load (taskschd.dll) from MS Office', 1, 2, 3, 'Persistence', 'T1053 - Scheduled Task/Job', 'Identifies a suspicious image load (taskschd.dll) from Microsoft Office processes. This behavior may indicate adversarial activity where a scheduled task is configured via Windows Component Object Model (COM). This technique can be used to configure persistence and evade monitoring by avoiding the usage of the traditional Windows binary (schtasks.exe) used to manage scheduled tasks.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1053/"]', 'regexMatch("log.eventDataProcessName", "(WINWORD.EXE|EXCEL.EXE|POWERPNT.EXE|MSPUB.EXE|MSACCESS.EXE)") && contains("log.message", "taskschd.dll")', '2026-02-23 16:17:06.220563', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (984, 'Windows: Suspicious Execution from mounted device', 1, 2, 3, 'Defense Evasion', 'T1055 - Process Injection', 'Identifies suspicious process access events from an unknown memory region. Endpoint security solutions usually hook userland Windows APIs in order to decide if the code that is being executed is malicious or not. It''s possible to bypass hooked functions by writing malicious functions that call syscalls directly.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1055/"]', 'equals("log.eventType", "start") && regexMatch("log.process.executable", "(^C:\\\\)") && regexMatch("log.processWorkingDirectory", "((^\\w:\\\\)") && !regexMatch("log.processWorkingDirectory", "(^C:\\\\)") && contains("log.processParentName", "explorer.exe") && oneOf("log.processName", ["rundll32.exe", "mshta.exe", "powershell.exe", "pwsh.exe", "cmd.exe", "regsvr32.exe", "cscript.exe", "wscript.exe"])', '2026-02-23 16:17:07.496247', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (985, 'Windows: New Windows Service Created to start from windows root path. Suspicious event as the binary may have been dropped using Windows Admin Shares', 1, 2, 3, 'Execution', 'T1021.002 - Remote Services: SMB/Windows Admin Shares', 'Adversaries may use Valid Accounts to interact with a remote network share using Server Message Block (SMB). The adversary may then perform actions as the logged-on user.', '["https://attack.mitre.org/techniques/T1021/002/"]', 'regexMatch("log.eventDataImagePath", "(^%systemroot%\\(.+)\\(.+).exe)") && equals("log.eventCode", 7045) && oneOf("log.logName", ["system", "System"])', '2026-02-23 16:17:08.698420', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (904, 'Windows Script Interpreter Executing Process via WMI', 2, 2, 2, 'Initial Access', 'T1566.001 - Phishing: Spearphishing Attachment', 'Identifies use of the built-in Windows script interpreters (cscript.exe or wscript.exe) being used to execute a process via Windows Management Instrumentation (WMI). This may be indicative of malicious activity.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1566/001/"]', 'oneOf("log.processName", ["wscript.exe", "cscript.exe"]) && contains("log.message", "wmiutils.dll")', '2026-02-23 16:15:37.737643', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (905, 'Windows: Remote Scheduled Task Creation', 2, 2, 2, 'Lateral Movement', 'T1021 - Remote Services', 'Identifies remote scheduled task creations on a target host. This could be indicative of adversary lateral movement.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/"]', 'contains("log.processName", "svchost.exe") && oneOf("log.eventDataSourceNetworkAddress", ["incoming", "ingress"]) && greaterOrEqual("origin.port", 49152) && greaterOrEqual("target.port", 49152) && !oneOf("origin.ip", ["127.0.0.1", "::1"])', '2026-02-23 16:15:38.847150', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (906, 'Windows Script Executing PowerShell', 2, 3, 2, 'Initial Access', 'T1566.001 - Phishing: Spearphishing Attachment', 'Identifies a PowerShell process launched by either cscript.exe or wscript.exe. Observing Windows scripting processes executing a PowerShell script, may be indicative of malicious activity.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1566/001/"]', 'equals("log.eventDataEventType", "start") && oneOf("log.eventDataParentProcessName", ["cscript.exe", "wscript.exe"]) && contains("log.eventDataProcessName", "powershell.exe")', '2026-02-23 16:15:40.032098', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (907, 'Windows: Searching for Saved Credentials via VaultCmd', 3, 2, 1, 'Credential Access', 'T1555 - Credentials from Password Stores', 'Windows Credential Manager allows you to create, view, or delete saved credentials for signing into websites, connected applications, and networks. An adversary may abuse this to list or dump credentials stored in the Credential Manager for saved usernames and passwords. This may also be performed in preparation of lateral movement.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1555/"]', 'contains("log.eventDataProcessName", "vaultcmd.exe") && contains("log.message", "/list")', '2026-02-23 16:15:41.126817', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (908, 'Windows: Multiple Vault Web Credentials Read', 2, 3, 2, 'Credential Access', 'T1555.004 - Credentials from Password Stores: Windows Credential Manager', 'Windows Credential Manager allows you to create, view, or delete saved credentials for signing into websites, connected applications, and networks. An adversary may abuse this to list or dump credentials stored in the Credential Manager for saved usernames and passwords. This may also be performed in preparation of lateral movement.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1555/004/"]', 'equals("log.eventCode", 5382) && !equals("log.eventDataSubjectLogonId", "0x3e7") && (contains("log.eventDataSchemaFriendlyName", "Windows Web Password Credential") || contains("log.eventDataResource", "http"))', '2026-02-23 16:15:42.273418', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataProcessPid","operator":"filter_term","value":"{{log.eventDataProcessPid}}"},{"field":"log.eventCode","operator":"filter_term","value":"5382"},{"field":"log.eventDataSubjectLogonId","operator":"filter_not_match","value":"0x3e7"}],"or":[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataSchemaFriendlyName","operator":"filter_term","value":"Windows Web Password Credential"},{"field":"log.eventDataResource","operator":"filter_term","value":"http"}],"or":null,"within":"now-60s","count":1}],"within":"now-60s","count":1}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (909, 'Windows: Outbound Scheduled Task Activity via PowerShell', 2, 3, 2, 'Execution', 'T1053.005 - Scheduled Task', 'Identifies the PowerShell process loading the Task Scheduler COM DLL followed by an outbound RPC network connection within a short time period. This may indicate lateral movement or remote discovery via scheduled tasks.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1053/005/"]', 'oneOf("log.eventDataProcessName", ["powershell.exe", "pwsh.exe", "powershell_ise.exe"]) && regexMatch("log.message", "(powershell.exe|pwsh.exe|powershell_ise.exe)")', '2026-02-23 16:15:43.406951', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (910, 'Windows: Potential Privileged Escalation via SamAccountName Spoofing', 2, 3, 1, 'Privilege Escalation', 'T1078 - Valid Accounts', 'Identifies a suspicious computer account name rename event, which may indicate an attempt to exploit CVE-2021-42278 to elevate privileges from a standard domain user to a user with domain admin privileges. CVE-2021-42278 is a security vulnerability that allows potential attackers to impersonate a domain controller via samAccountName attribute spoofing.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1078/"]', 'equals("action", "renamed-user-account") && endsWith("target.user", "$") && !endsWith("log.eventDataNewTargetUserName", "$")', '2026-02-23 16:15:44.589033', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (911, 'Windows: Execution of Persistent Suspicious Program', 2, 3, 2, 'Persistence', 'T1547 - Boot or Logon Autostart Execution', 'Identifies execution of suspicious persistent programs (scripts, rundll32, etc.) by looking at process lineage and command line usage', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1547/"]', 'contains("log.eventDataProcessName", "explorer.exe")', '2026-02-23 16:15:45.647899', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (912, 'Windows: Unusual Child Processes of RunDLL32', 2, 3, 2, 'Defense Evasion', 'T1218.011 - System Binary Proxy Execution: Rundll32', 'Identifies child processes of unusual instances of RunDLL32 where the command line parameters were suspicious. Misuse of RunDLL32 could indicate malicious activity', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1218/011/"]', 'contains("log.eventDataProcessName", "rundll32.exe")', '2026-02-23 16:15:46.787111', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (913, 'Windows: Remote Logon followed by Scheduled Task Creation', 3, 3, 2, 'Lateral Movement', 'T1021 - Remote Services', 'Identifies a remote logon followed by a scheduled task creation on the target host. This could be indicative of adversary lateral movement.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/"]', 'equals("log.action", "logged-in") && equals("actionResult", "success") && !contains("log.UserName", "ANONYMOUS LOGON") && !contains("log.UserDomain", "NT AUTHORITY")', '2026-02-23 16:15:47.931134', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.hostId","operator":"filter_term","value":"{{log.hostId}}"},{"field":"log.eventDataSubjectLogonId","operator":"filter_term","value":"scheduled-task-created"}],"or":null,"within":"now-60s","count":3}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (914, 'Windows: Remote System Discovery Commands', 3, 1, 2, 'Discovery', 'T1018 - Remote System Discovery', 'Discovery of remote system information using built-in commands, which may be used to move laterally.', '["https://attack.mitre.org/tactics/TA0007/","https://attack.mitre.org/techniques/T1018/"]', 'regexMatch("log.message", "(-n|-s|-a)") && regexMatch("log.eventDataProcessName", "(nbtstat.exe|arp.exe)")', '2026-02-23 16:15:48.940939', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (915, 'Windows: Remote File Download via MpCmdRun', 2, 3, 1, 'Command and Control', 'T1105 - Ingress Tool Transfer', 'Identifies the Windows Defender configuration utility (MpCmdRun.exe) being used to download a remote file.', '["https://attack.mitre.org/tactics/TA0011/","https://attack.mitre.org/techniques/T1105/"]', 'contains("log.eventDataProcessName", "MpCmdRun.exe") && contains("log.message", "-url") && contains("log.message", "-DownloadFile") && contains("log.message", "-path")', '2026-02-23 16:15:49.924130', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (916, 'Windows: Remote File Copy to a Hidden Share', 3, 2, 1, 'Lateral Movement', 'T1021 - Remote Services', 'Identifies a remote file copy attempt to a hidden network share. This may indicate lateral movement or data staging activity.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/"]', 'oneOf("log.eventDataProcessName", ["cmd.exe", "powershell.exe", "robocopy.exe", "xcopy.exe"]) && contains("log.message", "$") && (contains("log.message", "copy") || contains("log.message", "move") || contains("log.message", " cp ") || contains("log.message", " mv "))', '2026-02-23 16:15:50.930626', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (917, 'Windows: Possible ransomware attack detected. Ransomware Note Creation.', 3, 3, 2, 'Impact', 'T1486 - Data Encrypted for Impact', 'Ransomware, is a type of malware that prevents users from accessing their system or personal files and requires payment of a ransom in order to gain access to them again. Identifies ransomware attempts. A known ransomware note file has been detected, potentially indicating an active ransomware infection.', '["https://attack.mitre.org/tactics/TA0040/"]', 'equals("log.eventCode", 4663) && regexMatch("log.EventDataFileName", "(README_TO_RESTORE_FILES|INSTRUCTION_TO_GET_FILES_BACK|HOW_TO_DECRYPT_FILES|DECRYPT_INSTRUCTION|RECOVER_INSTRUCTION|RESTORE_FILES|READ_ME_NOW|YOUR_FILES_ARE_ENCRYPTED|IMPORTANT_INSTRUCTIONS|NOTICE|DECRYPT_YOUR_FILES|HOW_TO_RESTORE_FILES|HELP_DECRYPT|RECOVERY_FILE|RECOVER-FILES|INSTRUCTION)\\.(txt|html|php)$")', '2026-02-23 16:15:51.934608', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":"{{log.eventCode}}"},{"field":"log.EventDataFileName","operator":"filter_term","value":"{{log.EventDataFileName}}"}],"or":null,"within":"now-60s","count":5}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (918, 'Windows: Remote File Download via Desktopimgdownldr Utility', 2, 3, 1, 'Command and Control', 'T1105 - Ingress Tool Transfer', 'Identifies the desktopimgdownldr utility being used to download a remote file. An adversary may use desktopimgdownldr to download arbitrary files as an alternative to certutil.', '["https://attack.mitre.org/tactics/TA0011/","https://attack.mitre.org/techniques/T1105/"]', 'contains("log.eventDataProcessName", "desktopimgdownldr.exe")', '2026-02-23 16:15:52.940394', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (919, 'Windows: Possible ransomware attack detected. Multiple File Deletion.', 1, 3, 2, 'Impact', 'T1486 - Data Encrypted for Impact', 'Detects potential ransomware activity by monitoring multiple file write/modification events (Event ID 4663) with write access masks in user directories within a short timeframe. Modern ransomware typically encrypts files in-place rather than deleting them, making write access monitoring more effective than deletion monitoring alone.', '["https://attack.mitre.org/tactics/TA0040/"]', e'equals("log.eventCode", 4663) && +oneOf("log.eventDataAccessMask", ["0x2", "0x4", "0x6"]) && +!(regexMatch("log.eventDataProcessName", "(?i).*(trustedinstaller|svchost|wuauclt|msiexec|windows10upgrade|setuphost|tiworker|dism).*")) && +regexMatch("log.eventDataObjectName", "(?i).*\\\\\\\\(users|documents|desktop|downloads|pictures|videos|music)\\\\\\\\.*") && +!(regexMatch("log.eventDataObjectName", "(?i).*(\\\\\\\\windows\\\\\\\\|\\\\\\\\program files|\\\\\\\\programdata\\\\\\\\|\\\\\\\\temp\\\\\\\\|\\\\\\\\appdata\\\\\\\\local\\\\\\\\temp|\\\\\\\\softwaredistribution\\\\\\\\|\\\\\\\\winsxs\\\\\\\\|\\\\\\\\logs\\\\\\\\|\\\\\\\\prefetch\\\\\\\\).*")) && +!(regexMatch("log.eventDataObjectName", "(?i).*\\\\.(tmp|log|etl|dmp|pf|evtx|cache|dat|bak)$")) && +regexMatch("log.eventDataObjectName", "(?i).*\\\\.(doc[x]?|xls[x]?|ppt[x]?|pdf|txt|jpg|jpeg|png|gif|bmp|mp4|avi|mp3|zip|rar|7z)$") +', '2026-02-23 16:15:54.025061', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":"4663"},{"field":"origin.user","operator":"filter_term","value":"{{origin.user}}"}],"or":null,"within":"now-5m","count":50}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (920, 'Windows: Suspicious Microsoft Diagnostics Wizard Execution with args IT_RebrowseForFile=, ms-msdt:/id, ms-msdt:-id or FromBase64', 2, 3, 1, 'Defense Evasion', 'T1218 - System Binary Proxy Execution', 'Identifies potential abuse of the Microsoft Diagnostics Troubleshooting Wizard (MSDT) to proxy malicious command or binary execution via malicious process arguments', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1218/"]', 'contains("log.eventDataProcessName", "msdt.exe") && regexMatch("log.message", "(IT_RebrowseForFile=|ms-msdt:/id|ms-msdt:-id|FromBase64)")', '2026-02-23 16:15:55.116584', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (921, 'Windows: Privilege Escalation via Rogue Named Pipe Impersonation', 1, 3, 2, 'Privilege Escalation', 'T1134 - Access Token Manipulation', 'Identifies a privilege escalation attempt via rogue named pipe impersonation. An adversary may abuse this technique by masquerading as a known named pipe and manipulating a privileged process to connect to it.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1134/"]', 'regexMatch("log.eventDataProcessName", "(\\(.+)\\Pipe\\)") && contains("log.action", "Pipe Created")', '2026-02-23 16:15:56.315789', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (922, 'Windows: Potential Modification of Accessibility Binaries', 2, 3, 1, 'Persistence', 'T1546.008 - Event Triggered Execution: Accessibility Features', 'Windows contains accessibility features that may be launched with a key combination before a user has logged in. An adversary can modify the way these programs are launched to get a command prompt or backdoor without logging in to the system.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1546/008/"]', e'!regexMatch("log.eventDataProcessName", "(osk.exe|sethc.exe|utilman2.exe|DisplaySwitch.exe|ATBroker.exe|ScreenMagnifier.exe|SR.exe|Narrator.exe|magnify.exe|MAGNIFY.EXE)") && + regexMatch("log.eventDataParentProcessName", "(Utilman.exe|on.exe)") && + contains("log.eventDataSubjectUserName", "SYSTEM") && + regexMatch("log.message", "(C:\\\\Windows\\\\System32\\\\osk.exe|C:\\\\Windows\\\\System32\\\\Magnify.exe|C:\\\\Windows\\\\System32\\\\Narrator.exe|C:\\\\Windows\\\\System32\\\\Sethc.exe|utilman.exe|ATBroker.exe|DisplaySwitch.exe|sethc.exe)") +', '2026-02-23 16:15:57.522165', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (923, 'Windows: Suspicious PrintSpooler Service Executable File Creation', 2, 3, 1, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', 'Detects attempts to exploit privilege escalation vulnerabilities related to the Print Spooler service. For more information refer to the following CVE''s - CVE-2020-1048, CVE-2020-1337 and CVE-2020-1300 and verify that the impacted system is patched', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1068/"]', e'!regexMatch("log.file.path", "(\\\\Windows\\\\System32\\\\spool\\\\|:\\\\Windows\\\\Temp\\\\|:\\\\Users\\\\)") && contains("log.eventDataProcessName", "spoolsv.exe") +', '2026-02-23 16:15:58.649751', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (924, 'Windows Firewall Disabled via PowerShell', 1, 2, 3, 'Defense Evasion', 'T1562.004 - Impair Defenses: Disable or Modify System Firewall', 'Identifies when the Windows Firewall is disabled using PowerShell cmdlets, which can help attackers evade network constraints, like internet and network lateral communication restrictions.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/004/"]', 'regexMatch("log.message", "(-All|Public|Domain|Private)") && contains("log.message", "False") && contains("log.message", "-Enabled") && contains("log.message", "Set-NetFirewallProfile") && regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.exe|powershell_ise.exe)")', '2026-02-23 16:15:59.848021', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (925, 'Windows: Probable Password guessing', 2, 2, 3, 'Credential Access', 'T1110.001 - Brute Force: Password Guessing', 'Adversaries with no prior knowledge of legitimate credentials within the system or environment may guess passwords to attempt access to accounts. Without knowledge of the password for an account, an adversary may opt to systematically guess the password using a repetitive or iterative mechanism. An adversary may guess login credentials without prior knowledge of system or environment passwords during an operation by using a list of common passwords. Password guessing may or may not take into account the target''s policies on password complexity or use policies that may lock accounts out after a number of failed attempts.', '["https://attack.mitre.org/tactics/TA0006","https://attack.mitre.org/techniques/T1110/001/"]', 'oneOf("log.eventCode", [4625, 529, 530, 531, 532, 533, 534, 535, 536, 537, 539])', '2026-02-23 16:16:01.017499', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":"{{log.eventCode}}"},{"field":"target.user","operator":"filter_term","value":"{{target.user}}"}],"or":null,"within":"now-5m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (926, 'Windows: Persistence via WMI Event Subscription', 2, 3, 2, 'Persistence', 'T1546.003 - Event Triggered Execution: WMI Event Subscription', 'An adversary can use Windows Management Instrumentation (WMI) to install event filters, providers, consumers, and bindings that execute code when a defined event occurs. Adversaries may use the capabilities of WMI to subscribe to an event and execute arbitrary code when that event occurs, providing persistence on a system.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1546/003/"]', 'contains("log.message", "create") && regexMatch("log.message", "(ActiveScriptEventConsumer|CommandLineEventConsumer)") && contains("log.eventDataProcessName", "wmic.exe")', '2026-02-23 16:16:02.184850', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (927, 'Windows: Suspicious Print Spooler SPL File Created', 1, 3, 2, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', 'Detects attempts to exploit privilege escalation vulnerabilities related to the Print Spooler service including CVE-2020-1048 and CVE-2020-1337.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1068/"]', e'!regexMatch("log.eventDataProcessName", "(spoolsv.exe|printfilterpipelinesvc.exe|PrintIsolationHost.exe|splwow64.exe|msiexec.exe|poqexec.exe)") && regexMatch("log.eventDataProcessName", "(:\\\\Windows\\\\System32\\\\spool\\\\PRINTERS\\\\)") +', '2026-02-23 16:16:03.392862', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (928, 'Windows: Persistence via Update Orchestrator Service Hijack', 2, 3, 2, 'Persistence', 'T1543.003 - Create or Modify System Process: Windows Service', 'Identifies potential hijacking of the Microsoft Update Orchestrator Service to establish persistence with an integrity level of SYSTEM.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1543/003/"]', e'contains("log.message", "UsoSvc") && + regexMatch("log.eventDataParentProcessName", "C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\svchost\\\\.exe") && + !regexMatch("log.eventDataProcessName", "MoUsoCoreWorker\\\\.exe|OfficeC2RClient\\\\.exe") && + !regexMatch("log.eventDataProcessName", "ProgramData\\\\\\\\Microsoft\\\\\\\\Windows\\\\\\\\UUS\\\\\\\\Packages\\\\\\\\.*\\\\\\\\amd64\\\\\\\\MoUsoCoreWorker\\\\.exe|Windows\\\\\\\\System32\\\\\\\\UsoClient\\\\.exe|Windows\\\\\\\\System32\\\\\\\\MusNotification\\\\.exe|Windows\\\\\\\\System32\\\\\\\\MusNotificationUx\\\\.exe|Windows\\\\\\\\System32\\\\\\\\MusNotifyIcon\\\\.exe|Windows\\\\\\\\System32\\\\\\\\WerFault\\\\.exe|Windows\\\\\\\\System32\\\\\\\\WerMgr\\\\.exe|Windows\\\\\\\\UUS\\\\\\\\amd64\\\\\\\\MoUsoCoreWorker\\\\.exe|Windows\\\\\\\\System32\\\\\\\\MoUsoCoreWorker\\\\.exe|Windows\\\\\\\\UUS\\\\\\\\amd64\\\\\\\\UsoCoreWorker\\\\.exe|Windows\\\\\\\\System32\\\\\\\\UsoCoreWorker\\\\.exe|Program Files\\\\\\\\Common Files\\\\\\\\microsoft shared\\\\\\\\ClickToRun\\\\\\\\OfficeC2RClient\\\\.exe") +', '2026-02-23 16:16:04.566964', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (929, 'Windows: Persistence via TelemetryController Scheduled Task Hijack', 2, 3, 2, 'Persistence', 'T1053.005 - Scheduled Task', 'Detects the successful hijack of Microsoft Compatibility Appraiser scheduled task to establish persistence with an integrity level of system.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1053/005/"]', 'contains("log.message", "-cv") && contains("log.eventDataParentProcessName", "CompatTelRunner.exe") && !(regexMatch("log.eventDataProcessName", "(conhost.exe|DeviceCensus.exe|CompatTelRunner.exe|DismHost.exe|rundll32.exe|powershell.exe)"))', '2026-02-23 16:16:05.746209', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (930, 'Windows: Persistence via BITS Job Notify Cmdline', 2, 3, 2, 'Persistence', 'T1197 - BITS Jobs', 'An adversary can use the Background Intelligent Transfer Service (BITS) SetNotifyCmdLine method to execute a program that runs after a job finishes transferring data or after a job enters a specified state in order to persist on a system.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1197/"]', 'contains("log.message", "BITS") && contains("log.eventDataParentProcessName", "svchost.exe") && !(regexMatch("log.eventDataProcessName", "(:\\Windows\\System32\\WerFaultSecure.exe|:\\Windows\\System32\\WerFault.exe|:\\Windows\\System32\\wermgr.exe|:\\WINDOWS\\system32\\directxdatabaseupdater.exe)"))', '2026-02-23 16:16:06.933567', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (931, 'Windows: Privilege Escalation via Named Pipe Impersonation', 3, 3, 2, 'Privilege Escalation', 'T1134 - Access Token Manipulation', 'Identifies a privilege escalation attempt via named pipe impersonation. An adversary may abuse this technique by utilizing a framework such Metasploit''s meterpreter getsystem command.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1134/"]', 'regexMatch("log.eventDataProcessName", "(Cmd.Exe|PowerShell.EXE|powershell.exe|cmd.exe)") && contains("log.message", ">") && regexMatch("log.message", "(\\\\.\\pipe\\)")', '2026-02-23 16:16:08.132646', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (932, 'Windows: User logged using Remote Desktop Connection from loopback address, possible exploit over reverse tunneling using stolen credentials', 3, 2, 1, 'Credential Access', 'T1021.001 - Remote Services: Remote Desktop Protocol', 'Adversaries may use Valid Accounts to log into a computer using the Remote Desktop Protocol (RDP). The adversary may then perform actions as the logged-on user.', '["https://attack.mitre.org/techniques/T1021/001/"]', 'equals("log.eventDataLogonType", "10") && oneOf("log.origin.ips", ["::1", "127.0.0.1"]) && oneOf("log.eventCode", [528, 540, 673, 4624, 4769])', '2026-02-23 16:16:09.305307', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (933, 'Windows: Multiple failed attempts to perform a privileged operation by the same user', 1, 2, 3, 'Privilege Escalation', 'T1110 - Brute Force', 'Adversaries may use brute force techniques to gain access to accounts when passwords are unknown or when password hashes are obtained.', '["https://attack.mitre.org/techniques/T1110/"]', 'equals("log.eventCode", 577) || equals("log.eventCode", 4673)', '2026-02-23 16:16:10.480165', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"target.user","operator":"filter_term","value":"{{target.user}}"}],"or":null,"within":"now-10m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (934, 'Windows: Modification of Boot Configuration', 1, 3, 3, 'Impact', 'T1490 - Inhibit System Recovery', 'Identifies use of bcdedit.exe to delete boot configuration data. This tactic is sometimes used as by malware or an attacker as a destructive technique.', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1490/"]', 'contains("log.eventDataProcessName", "bcdedit.exe") && regexMatch("log.message", "((ignoreallfailures(.+)bootstatuspolicy(.+)/set)|(ignoreallfailures(.+)/set(.+)bootstatuspolicy)|(/set(.+)bootstatuspolicy(.+)ignoreallfailures)|(/set(.+)ignoreallfailures(.+)bootstatuspolicy)|(bootstatuspolicy(.+)set(.+)ignoreallfailures)|(bootstatuspolicy(.+)ignoreallfailures(.+)/set)|(no(.+)recoveryenabled)|(recoveryenabled(.+)no))")', '2026-02-23 16:16:11.487276', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (935, 'Windows: Mounting Hidden or WebDav Remote Shares', 3, 2, 1, 'Lateral Movement', 'T1021.002 - Remote Services: SMB/Windows Admin Shares', 'Identifies the use of net.exe to mount a WebDav or hidden remote share. This may indicate lateral movement or preparation for data exfiltration.', '["https://attack.mitre.org/tactics/TA0008/","https://attack.mitre.org/techniques/T1021/002/"]', 'regexMatch("log.eventDataProcessName", "(net.exe|net1.exe)") && regexMatch("log.message", "(\\\\(.+)\\(.+)$|\\\\(.+)@SSL\\|http)") && contains("log.message", "/d") && contains("log.message", "use") && !(contains("log.eventDataParentProcessName", "net.exe"))', '2026-02-23 16:16:12.673631', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (936, 'Windows: Microsoft security essentials - Virus detected', 3, 3, 2, 'Privilege Escalation', 'T1055 - Process Injection', 'Detect the presence of a virus or malware on the system using Microsoft Security Essentials. The rule correlates different threat detection events, represented by various Event IDs, to identify virus detection on the system.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1055/"]', 'oneOf("log.eventCode", [1107, 1117, 1116, 1118, 1119]) && equals("log.providerName", "Microsoft Antimalware")', '2026-02-23 16:16:13.843357', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (937, 'Windows: LSASS Memory Dump Handle Access', 2, 3, 3, 'Credential Access', 'T1003.001 - OS Credential Dumping: LSASS Memory', 'Identifies handle requests for the Local Security Authority Subsystem Service (LSASS) object access with specific access masks that many tools with a capability to dump memory to disk use (0x1fffff, 0x1010, 0x120089). This rule is tool agnostic as it has been validated against a host of various LSASS dump tools such as SharpDump, Procdump, Mimikatz, Comsvcs etc. It detects this behavior at a low level and does not depend on a specific tool or dump file name.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/","https://attack.mitre.org/techniques/T1003/001/"]', 'equals("log.eventCode", 4656) && regexMatch("log.eventDataObjectName", "(:\\Windows\\System32\\lsass.exe|\\Device\\HarddiskVolume[A-Za-z?:\\]([A-Za-z?])?\\Windows\\System32\\lsass.exe)") && !regexMatch("log.eventDataProcessName", "(:\\Program Files\\(.+).exe|:\\Program Files (x86)\\(.+).exe|:\\Windows\\system32\\wbem\\WmiPrvSE.exe|:\\Windows\\System32\\dllhost.exe|:\\Windows\\System32\\svchost.exe|:\\Windows\\System32\\msiexec.exe|:\\ProgramData\\Microsoft\\Windows Defender\\(.+).exe|:\\Windows\\explorer.exe)") && oneOf("log.eventDataAccessMask", ["0x1fffff", "0x1010", "0x120089", "0x1F3FFF"])', '2026-02-23 16:16:14.943525', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (938, 'Windows: IIS HTTP Logging Disabled', 3, 2, 3, 'Defense Evasion', 'T1562.002 - Impair Defenses: Disable Windows Event Logging', 'Identifies when Internet Information Services (IIS) HTTP Logging is disabled on a server. An attacker with IIS server access via a webshell or other mechanism can disable HTTP Logging as an effective anti-forensics measure.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/002/"]', 'contains("log.eventDataProcessName", "appcmd.exe") && regexMatch("log.message", "(/dontLog(.+):(.+)True)") && !(contains("log.eventDataParentProcessName", "iissetup.exe"))', '2026-02-23 16:16:16.133286', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (939, 'Windows: Microsoft IIS Connection Strings Decryption', 2, 3, 1, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies use of aspnet_regiis to decrypt Microsoft IIS connection strings. An attacker with Microsoft IIS web server access via a webshell or alike can decrypt and dump any hardcoded connection strings, such as the MSSQL service account password using aspnet_regiis command.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'regexMatch("log.eventDataProcessName", "aspnet_regiis.exe") && regexMatch("log.message", "(connectionStrings)") && regexMatch("log.message", "(-pdf)")', '2026-02-23 16:16:17.307172', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (940, 'Windows: Microsoft IIS Service Account Password Dumped', 3, 2, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies the Internet Information Services (IIS) command-line tool, AppCmd, being used to list passwords. An attacker with IIS web server access via a web shell can decrypt and dump the IIS AppPool service account password using AppCmd.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'regexMatch("log.eventDataProcessName", "appcmd.exe") && regexMatch("log.message", "(/list)") && regexMatch("log.message", "(/text(.+)password)")', '2026-02-23 16:16:18.489030', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (941, 'Windows: Execution via MSSQL xp_cmdshell Stored Procedure', 3, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', 'Identifies execution via MSSQL xp_cmdshell stored procedure. Malicious users may attempt to elevate their privileges by using xp_cmdshell, which is disabled by default, thus, it''s important to review the context of it''s use.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1059/"]', 'oneOf("log.message", ["diskfree", "rmdir", "mkdir", "dir", "del", "rename", "bcp", "XMLNAMESPACES"]) && contains("log.eventDataProcessName", "cmd.exe") && contains("log.eventDataParentProcessName", "sqlservr.exe")', '2026-02-23 16:16:19.539166', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (942, 'Windows: Process Activity via Compiled HTML File', 3, 3, 1, 'Execution', 'T1204.002 - User Execution: Malicious File', 'Compiled HTML files (.chm) are commonly distributed as part of the Microsoft HTML Help system. Adversaries may conceal malicious code in a CHM file and deliver it to a victim for execution. CHM content is loaded by the HTML Help executable program (hh.exe).', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1204/002/"]', 'regexMatch("log.eventDataProcessName", "(mshta.exe|cmd.exe|powershell.exe|pwsh.exe|powershell_ise.exe|cscript.exe|wscript.exe)") && regexMatch("log.eventDataParentProcessName", "hh.exe")', '2026-02-23 16:16:20.713047', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (943, 'Windows: Possible sideloading of DLL via Microsoft antimalware service executable with MsMpEng process', 3, 3, 3, 'Defense Evasion', 'T1574 - Hijack Execution Flow', 'Identifies a Windows trusted program that is known to be vulnerable to DLL Search Order Hijacking starting after being renamed or from a non-standard path. This is uncommon behavior and may indicate an attempt to evade defenses via side-loading a malicious DLL within the memory space of one of those processes.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1574/"]', 'contains("log.eventDataProcessName", "MsMpEng.exe") && !regexMatch("log.eventDataProcessPath", "(:\\ProgramData\\Microsoft\\Windows Defender\\(.+).exe|:\\Program Files\\Windows Defender\\(.+).exe|:\\Program Files (x86)\\Windows Defender\\(.+).exe|:\\Program Files\\Microsoft Security Client\\(.+).exe|:\\Program Files (x86)\\Microsoft Security Client\\(.+).exe)")', '2026-02-23 16:16:21.846333', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (944, 'Windows: Microsoft Build Engine Started by a System Process', 3, 3, 2, 'Defense Evasion', 'T1127.001 - Trusted Developer Utilities Proxy Execution: MSBuild', 'An instance of MSBuild, the Microsoft Build Engine, was started by Explorer or the WMI (Windows Management Instrumentation) subsystem. This behavior is unusual and is sometimes used by malicious payloads.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1127/001/"]', 'regexMatch("log.eventDataProcessName", "MSBuild.exe") && regexMatch("log.eventDataParentProcessName", "(explorer.exe|wmiprvse.exe)")', '2026-02-23 16:16:22.853607', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (945, 'Windows: Microsoft Build Engine Started an Unusual Process', 3, 3, 2, 'Defense Evasion', 'T1027 - Obfuscated Files or Information', 'An instance of MSBuild, the Microsoft Build Engine, started a PowerShell script or the Visual C# Command Line Compiler. This technique is sometimes used to deploy a malicious payload using the Build Engine.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1027/"]', 'regexMatch("log.eventDataProcessName", "(csc.exe|iexplore.exe|powershell.exe)") && regexMatch("log.eventDataParentProcessName", "MSBuild.exe")', '2026-02-23 16:16:23.959123', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (946, 'Windows: Microsoft Build Engine Started by a Script Process', 3, 3, 2, 'Defense Evasion', 'T1127.001 - Trusted Developer Utilities Proxy Execution: MSBuild', 'An instance of MSBuild, the Microsoft Build Engine, was started by a script or the Windows command interpreter. This behavior is unusual and is sometimes used by malicious payloads.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1127/001/"]', 'regexMatch("log.eventDataProcessName", "MSBuild.exe") && regexMatch("log.eventDataParentProcessName", "(cmd.exe|powershell.exe|wscript.exe|cscript.exe)")', '2026-02-23 16:16:24.937434', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (947, 'Windows: Microsoft Build Engine Started by an Office Application', 3, 3, 1, 'Defense Evasion', 'T1127.001 - Trusted Developer Utilities Proxy Execution: MSBuild', 'An instance of MSBuild, the Microsoft Build Engine, was started by Excel or Word. This is unusual behavior for the Build Engine and could have been caused by an Excel or Word document executing a malicious script payload.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1127/001/"]', 'regexMatch("log.eventDataProcessName", "MSBuild.exe") && regexMatch("log.eventDataParentProcessName", "(eqnedt32.exe|excel.exe|fltldr.exe|msaccess.exe|mspub.exe|outlook.exe|powerpnt.exe|winword.exe)")', '2026-02-23 16:16:26.000190', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (948, 'Windows: Remote Desktop Enabled in Windows Firewall by Netsh', 3, 3, 1, 'Defense Evasion', 'T1562.004 - Impair Defenses: Disable or Modify System Firewall', 'Identifies use of the network shell utility (netsh.exe) to enable inbound Remote Desktop Protocol (RDP) connections in the Windows Firewall.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/004/"]', 'regexMatch("log.eventDataProcessName", "netsh.exe") && regexMatch("log.message", "(action=allow|enable=Yes|enable)") && regexMatch("log.message", "(localport=3389|RemoteDesktop|group=(.+)remote desktop(.+))")', '2026-02-23 16:16:27.028894', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (949, 'Windows: Detection of Exchange mail exported through PowerShell', 3, 1, 1, 'Collection', 'T1005 - Data from Local System', 'This rule identifies the use of an Exchange PowerShell cmdlet, which is used to export the contents of a core file. mailbox or archive to a .pst file. Adversaries can target user email to collect sensitive information.', '["https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1005/","https://attack.mitre.org/techniques/T1114/","https://attack.mitre.org/techniques/T1114/002/"]', 'regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.exe|powershell_ise.exe)") && regexMatch("log.message", "(New-MailboxExportRequest)")', '2026-02-23 16:16:28.087132', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (950, 'Windows: Credential Acquisition via Registry Hive Dumping', 3, 1, 1, 'Credential Access', 'T1003.002 - OS Credential Dumping: Security Account Manager', 'Identifies attempts to export a registry hive which may contain credentials using the Windows reg.exe tool.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/","https://attack.mitre.org/techniques/T1003/002/"]', 'regexMatch("log.eventDataProcessName", "reg.exe") && regexMatch("log.message", "(save|export)") && regexMatch("log.message", "(hklm\\sam|hklm\\security)")', '2026-02-23 16:16:29.177259', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (951, 'Windows: Disabling Windows Defender Security Settings via PowerShell', 3, 3, 3, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', 'Identifies use of the Set-MpPreference PowerShell command to disable or weaken certain Windows Defender settings.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/001/"]', 'regexMatch("log.message", "(Set-MpPreference)") && regexMatch("log.message", "(-Disable|Disabled|NeverSend|-Exclusion)") && regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.dll|powershell_ise.exe)")', '2026-02-23 16:16:30.280741', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (952, 'Windows: Disable Windows Firewall Rules via Netsh', 2, 2, 3, 'Defense Evasion', 'T1562.004 - Impair Defenses: Disable or Modify System Firewall', 'Identifies use of netsh.exe to disable Windows Firewall rules or turn off the firewall entirely. Adversaries may disable the Windows Firewall to enable network connections for lateral movement or command and control.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/004/"]', 'regexMatch("log.message", "(disable(.+)firewall(.+)set|disable(.+)set(.+)firewall|firewall(.+)disable(.+)set|firewall(.+)set(.+)disable|set(.+)disable(.+)firewall|set(.+)firewall(.+)disable|state(.+)advfirewall(.+)off|state(.+)off(.+)advfirewall|advfirewall(.+)state(.+)off|advfirewall(.+)off(.+)state|off(.+)state(.+)advfirewall|off(.+)advfirewall(.+)state)") && regexMatch("log.eventDataProcessName", "netsh.exe")', '2026-02-23 16:16:31.291436', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (953, 'Windows: PowerShell Keylogging Script', 3, 2, 2, 'Collection', 'T1056.001 - Input Capture: Keylogging', 'Detects the use of Win32 API Functions that can be used to capture user keystrokes in PowerShell scripts. Attackers use this technique to capture user input, looking for credentials and/or other valuable data.', '["https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1056/","https://attack.mitre.org/techniques/T1056/001/"]', 'regexMatch("log.message", "(GetAsyncKeyState|NtUserGetAsyncKeyState|GetKeyboardState|Get-Keystrokes|SetWindowsHookA|SetWindowsHookW|SetWindowsHookEx|SetWindowsHookExA|NtUserSetWindowsHookEx|GetForegroundWindow|GetWindowTextA|GetWindowTextW|WM_KEYBOARD_LL)") && regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.exe|powershell_ise.exe)")', '2026-02-23 16:16:32.462863', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (954, 'Windows: Deleting Backup Catalogs with Wbadmin', 1, 2, 3, 'Impact', 'T1490 - Inhibit System Recovery', 'Identifies use of the wbadmin.exe to delete the backup catalog. Ransomware and other malware may do this to prevent system recovery.', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1490/"]', 'regexMatch("log.message", "(delete(.+)catalog|catalog(.+)delete)") && regexMatch("log.eventDataProcessName", "(wbadmin.exe|WBADMIN.EXE)")', '2026-02-23 16:16:33.638347', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (955, 'Windows: Delete Volume USN Journal with Fsutil', 1, 2, 3, 'Defense Evasion', 'T1070.004 - Indicator Removal: File Deletion', 'Identifies use of the fsutil.exe to delete the volume USNJRNL. This technique is used by attackers to eliminate evidence of files created during post-exploitation activities.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1070/004/"]', 'regexMatch("log.message", "(deletejournal(.+)usn|usn(.+)deletejournal)") && regexMatch("log.eventDataProcessName", "fsutil.exe")', '2026-02-23 16:16:34.810106', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (956, 'Windows Defender Exclusions Added via PowerShell', 2, 2, 3, 'Defense Evasion', 'T1562 - Impair Defenses', 'Identifies modifications to the Windows Defender configuration settings using PowerShell to add exclusions at the folder directory or process level.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/"]', 'regexMatch("log.message", "(-Exclusion(.+)(Add-MpPreference|Set-MpPreference)|(Add-MpPreference|Set-MpPreference)(.+)-Exclusion)") && regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.exe|powershell_ise.exe)")', '2026-02-23 16:16:35.961367', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (957, 'Windows: Potential Credential Access via Trusted Developer Utility', 2, 2, 3, 'Credential Access', 'T1003 - OS Credential Dumping', 'An instance of MSBuild, the Microsoft Build Engine, loaded DLLs (dynamically linked libraries) responsible for Windows credential management. This technique is sometimes used for credential dumping.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'regexMatch("log.eventDataProcessName", "(MSBuild.exe|msbuild.exe)") && regexMatch("log.message", "(vaultcli.dll|SAMLib.DLL)")', '2026-02-23 16:16:37.137901', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataProcessId","operator":"filter_term","value":"{{log.eventDataProcessId}}"}],"or":null,"within":"now-1m","count":1}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (958, 'Windows: NTDS or SAM Database File Copied', 3, 3, 3, 'Credential Access', 'T1003.002 - OS Credential Dumping: Security Account Manager', 'Identifies a copy operation of the Active Directory Domain Database or Security Account Manager (SAM) files. Those files contain sensitive information including hashed domain and local credentials.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/","https://attack.mitre.org/techniques/T1003/002/"]', 'regexMatch("log.eventDataProcessName", "(?i)(cmd\\.exe|powershell\\.exe|xcopy\\.exe|esentutl\\.exe)") && regexMatch("log.message", "(copy|xcopy|Copy-Item|move|cp|mv|/y|/vss|/d)") && regexMatch("log.message", "(\\ntds.dit|\\config\\SAM|\\(.+)\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy(.+)\\|/system32/config/SAM)")', '2026-02-23 16:16:38.300804', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (959, 'Clearing Windows Event Logs with wevtutil', 1, 2, 3, 'Defense Evasion', 'T1070.001 - Indicator Removal: Clear Windows Event Logs', 'Identifies attempts to clear or disable Windows event log stores using Windows wevetutil command. This is often done by attackers in an attempt to evade detection or destroy forensic evidence on a system.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1070/001/"]', 'regexMatch("log.message", "(/e:false|cl|clear-log|Clear-EventLog)") && regexMatch("log.eventDataLogonProcessName", "wevtutil.exe")', '2026-02-23 16:16:39.433313', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (960, 'Windows Service Installed via an Unusual Client', 3, 3, 2, 'Privilege Escalation', 'T1543.003 - Create or Modify System Process: Windows Service', 'Identifies the creation of a Windows service by an unusual client process. Services may be created with administrator privileges but are executed under SYSTEM privileges, so an adversary may also use a service to escalate privileges from administrator to SYSTEM.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1543/003/"]', 'equals("action", "service-installed") && equals("clientProcessId", "0") && equals("parentProcessId", "0")', '2026-02-23 16:16:40.564801', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (961, 'Windows: Whoami Process Activity', 1, 1, 0, 'Discovery', 'T1033 - System Owner/User Discovery', 'Identifies suspicious use of whoami.exe which displays user, group, and privileges information for the user who is currently logged on to the local system.', '["https://attack.mitre.org/tactics/TA0007/","https://attack.mitre.org/techniques/T1033/"]', 'contains("log.eventDataProcessName", "whoami.exe")', '2026-02-23 16:16:41.781533', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (962, 'Windows Defender: Antimalware engine found malware or other potentially unwanted software', 1, 2, 3, 'Execution', 'T1546 - Event Triggered Execution', 'This rule is triggered when the antimalware engine detects malware or potentially unwanted software on the system. This alert is critical to identify the presence of threats and unwanted software that may compromise system security and performance.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1546/"]', 'oneOf("log.eventCode", [1006, 1015, 1116]) && equals("log.providerName", "SecurityCenter")', '2026-02-23 16:16:42.945957', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (963, 'Windows: Unusual Service Host Child Process - Childless Service', 1, 3, 2, 'Privilege Escalation', 'T1055 - Process Injection', 'Identifies unusual child processes of Service Host (svchost.exe) that traditionally do not spawn any child processes. This may indicate a code injection or an equivalent form of exploitation.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1055/"]', 'contains("log.eventDataParentProcessName", "svchost.exe") && regexMatch("log.message", "(WdiSystemHost|LicenseManager|StorSvc|CDPSvc|cdbhsvc|BthAvctpSvc|SstpSvc|WdiServiceHost|imgsvc|TrkWks|WpnService|IKEEXT|PolicyAgent|CryptSvc|netprofm|ProfSvc|StateRepository|camsvc|LanmanWorkstation|NlaSvc|EventLog|hidserv|DisplayEnhancementService|ShellHWDetection|AppHostSvc|fhsvc|CscService|PushToInstall)") && !regexMatch("log.eventDataProcessName", "(WerFault.exe|WerFaultSecure.exe|wermgr.exe|rundll32.exe)") && !regexMatch("log.eventDataProcessName", "(:\\Windows\\System32\\RelPost.exe|:\\Program Files\\|:\\Program Files (x86)\\|:\\Windows\\System32\\Kodak\\kds_i4x50\\lib\\lexexe.exe)") && !regexMatch("log.message", "(WdiSystemHost|WdiServiceHost|imgsvc)") && !regexMatch("log.message", "(:\\WINDOWS\\System32\\winethc.dll,ForceProxyDetectionOnNextRun)")', '2026-02-23 16:16:44.121771', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (964, 'Windows: Unusual Print Spooler Child Process', 1, 3, 2, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', 'Detects unusual Print Spooler service (spoolsv.exe) child processes. This may indicate an attempt to exploit privilege escalation vulnerabilities related to the Printing Service on Windows.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1068/"]', 'contains("log.eventDataParentProcessName", "spoolsv.exe") && !regexMatch("log.eventDataProcessName", "(splwow64.exe|PDFCreator.exe|acrodist.exe|spoolsv.exe|msiexec.exe|route.exe|WerFault.exe|net.exe|cmd.exe|powershell.exe|netsh.exe|regsvr32.exe)") && !regexMatch("log.message", "(\\WINDOWS\\system32\\spool\\DRIVERS|stop|start|.spl|\\program files(.+)route add|add portopening|rule name|PrintConfig.dll)") && equals("log.logName", "System")', '2026-02-23 16:16:45.295328', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (965, 'Windows: Unusual Network Connection via DllHost or via RunDLL32', 2, 3, 2, 'Defense Evasion', 'T1218 - System Binary Proxy Execution', 'Identifies unusual instances of dllhost.exe making outbound network connections. This may indicate adversarial Command and Control activity. Identifies unusual instances of rundll32.exe making outbound network connections. This may indicate adversarial Command and Control activity.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1218/"]', 'regexMatch("log.eventDataProcessName", "(dllhost.exe|rundll32.exe)") && regexMatch("log.origin.ips", "((10.0.0.0/8,127.0.0.0/8,169.254.0.0/16,172.16.0.0/12,192.0.0.0/24,192.0.0.0/29,192.0.0.8/32,192.0.0.9/32,192.0.0.10/32,192.0.0.170/32,192.0.0.171/32,192.0.2.0/24,192.31.196.0/24,192.52.193.0/24,192.168.0.0/16,192.88.99.0/24,224.0.0.0/4,100.64.0.0/10,192.175.48.0/24,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,240.0.0.0/4,::1,FE80::/10,FF00::/8)")', '2026-02-23 16:16:46.427287', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (966, 'Windows: Unusual File Modification by dns.exe', 1, 3, 2, 'Initial Access', 'T1133 - External Remote Services', 'Identifies an unexpected file being modified by dns.exe, the process responsible for Windows DNS Server services, which may indicate activity related to remote code execution or other forms of exploitation.', '["https://attack.mitre.org/tactics/TA0001/","https://attack.mitre.org/techniques/T1133/"]', 'contains("log.eventDataProcessName", "dns.exe") && !contains("log.eventDataFileName", "dns.log")', '2026-02-23 16:16:47.600711', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (967, 'Windows: UAC Bypass via Windows Firewall Snap-In Hijack', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies attempts to bypass User Account Control (UAC) by hijacking the Microsoft Management Console (MMC) Windows Firewall snap-in. Attackers bypass UAC to stealthily execute code with elevated permissions.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'contains("log.eventDataProcessName", "mmc.exe") && !contains("log.message", "WerFault.exe") && contains("log.message", "WF.msc")', '2026-02-23 16:16:48.750297', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (968, 'Windows: Unusual File Creation - Alternate Data Stream', 1, 3, 2, 'Defense Evasion', 'T1564 - Hide Artifacts', 'Identifies suspicious creation of Alternate Data Streams on highly targeted files. This is uncommon for legitimate files and sometimes done by adversaries to hide malware.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1564/"]', 'regexMatch("log.eventDataFileName", "(^C:\\(.+):(.+))") && !regexMatch("log.eventDataFileName", "(C:\\(.+):zone.identifier)") && regexMatch("log.message", "(pdf|dll|png|exe|dat|com|bat|cmd|sys|vbs|ps1|hta|txt|vbe|js|wsh|docx|doc|xlsx|xls|pptx|ppt|rtf|gif|jpg|png|bmp|img|iso)") && !regexMatch("log.eventDataProcessName", "(:\\windows\\System32\\svchost.exe|:\\Windows\\System32\\inetsrv\\w3wp.exe|:\\Windows\\explorer.exe|:\\Windows\\System32\\sihost.exe|:\\Windows\\System32\\PickerHost.exe|:\\Windows\\System32\\SearchProtocolHost.exe|:\\Program Files (x86)\\Dropbox\\Client\\Dropbox.exe|:\\Program Files\\Rivet Networks\\SmartByte\\SmartByteNetworkService.exe|:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe|:\\Program Files\\ExpressConnect\\ExpressConnectNetworkService.exe|:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe|:\\Program Files\\Google\\Chrome\\Application\\chrome.exe|:\\Program Files\\Mozilla Firefox\\firefox.exe)")', '2026-02-23 16:16:49.933149', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (969, 'Windows Web Shell Detection: Script Process Child of Common Web Processes', 1, 3, 2, 'Persistence', 'T1505.003 - Server Software Component: Web Shell', 'Identifies suspicious commands executed via a web server, which may suggest a vulnerability and remote shell access.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1505/003/"]', 'regexMatch("log.eventDataProcessName", "(cmd.exe|cscript.exe|powershell.exe|pwsh.exe|powershell_ise.exe|wmic.exe|wscript.exe)") && regexMatch("log.eventDataParentProcessName", "(w3wp.exe|httpd.exe|nginx.exe|php.exe|php-cgi.exe|tomcat.exe)")', '2026-02-23 16:16:51.108857', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (970, 'Windows: Volume Shadow Copy Deletion via WMIC', 1, 2, 3, 'Impact', 'T1490 - Inhibit System Recovery', 'Identifies use of wmic.exe for shadow copy deletion on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1490/"]', 'regexMatch("log.message", "(delete(.+)shadowcopy|shadowcopy(.+)delete)") && contains("log.eventDataProcessName", "WMIC.exe")', '2026-02-23 16:16:52.173672', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (971, 'Windows: Clearing Windows Console History', 1, 2, 3, 'Defense Evasion', 'T1070.003 - Indicator Removal: Clear Command History', 'Identifies when a user attempts to clear console history. An adversary may clear the command history of a compromised account to conceal the actions undertaken during an intrusion.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1070/003/"]', 'regexMatch("log.message", "(Clear-History|(Remove-Item|rm)(.+)(ConsoleHost_history.txt|\\(Get-PSReadlineOption\\)\\.HistorySavePath)|(ConsoleHost_history.txt|\\(Get-PSReadlineOption\\)\\.HistorySavePath)(.+)(Remove-Item|rm)|Set-PSReadlineOption(.+)SaveNothing|SaveNothing(.+)PSReadlineOption)") && equals("log.providerName", "PowerShell")', '2026-02-23 16:16:53.352646', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (972, 'Windows: Volume Shadow Copy Deletion via PowerShell', 1, 2, 3, 'Impact', 'T1490 - Inhibit System Recovery', 'Identifies the use of the Win32_ShadowCopy class and related cmdlets to achieve shadow copy deletion. This commonly occurs in tandem with ransomware or other destructive attacks.', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1490/"]', 'regexMatch("log.eventDataProcessName", "(powershell.exe|pwsh.exe|powershell_ise.exe)") && regexMatch("log.message", "(Get-WmiObject|gwmi|Get-CimInstance|gcim)") && regexMatch("log.message", "(Win32_ShadowCopy)") && regexMatch("log.message", "(\\.Delete\\(\\)|Remove-WmiObject|rwmi|Remove-CimInstance|rcim)")', '2026-02-23 16:16:54.461749', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (973, 'Windows: Volume Shadow Copy Deleted or Resized via VssAdmin', 1, 2, 3, 'Impact', 'T1490 - Inhibit System Recovery', 'Identifies use of vssadmin.exe for shadow copy deletion or resizing on endpoints. This commonly occurs in tandem with ransomware or other destructive attacks.', '["https://attack.mitre.org/tactics/TA0040/","https://attack.mitre.org/techniques/T1490/"]', 'regexMatch("log.message", "((delete|resize)(.+)shadows|shadows(.+)(delete|resize))") && contains("log.eventDataProcessName", "vssadmin.exe")', '2026-02-23 16:16:55.552078', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (974, 'Windows: Bypass UAC via Event Viewer', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies User Account Control (UAC) bypass via eventvwr.exe. Attackers bypass UAC to stealthily execute code with elevated permissions.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'contains("log.eventDataParentProcessName", "eventvwr.exe") && !regexMatch("log.eventDataProcessName", "(:\\Windows\\SysWOW64\\mmc.exe|:\\Windows\\System32\\mmc.exe|:\\Windows\\SysWOW64\\WerFault.exe|:\\Windows\\System32\\WerFault.exe)")', '2026-02-23 16:16:56.691201', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (975, 'Windows: UAC Bypass Attempt via Privileged IFileOperation COM Interface', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies attempts to bypass User Account Control (UAC) via DLL side-loading. Attackers may attempt to bypass UAC to stealthily execute code with elevated permissions.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'regexMatch("log.eventDataFileName", "(wow64log.dll|comctl32.dll|DismCore.dll|OskSupport.dll|duser.dll|Accessibility.ni.dll)") && contains("log.eventDataProcessName", "dllhost.exe") && !regexMatch("log.eventDataFileName", "(C:\\Windows\\SoftwareDistribution\\|C:\\Windows\\WinSxS\\)")', '2026-02-23 16:16:57.641068', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (976, 'Windows: UAC Bypass via ICMLuaUtil Elevated COM Interface', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies User Account Control (UAC) bypass attempts via the ICMLuaUtil Elevated COM interface. Attackers may attempt to bypass UAC to stealthily execute code with elevated permissions', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'contains("log.eventDataProcessName", "dllhost.exe") && !contains("log.message", "WerFault.exe") && regexMatch("log.message", "(/Processid:\\{3E5FC7F9-9A51-4367-9063-A120244FBEC7\\}|/Processid:\\{D2E7041B-2927-42FB-8E9F-7CE93B6DC937\\})")', '2026-02-23 16:16:58.684760', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (977, 'Windows: UAC Bypass Attempt via Elevated COM Internet Explorer Add-On Installer', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies User Account Control (UAC) bypass attempts by abusing an elevated COM Interface to launch a malicious program. Attackers may attempt to bypass UAC to stealthily execute code with elevated permissions.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'regexMatch("log.message", "(C:\\(.+)\\AppData\\(.+)\\Temp\\IDC(.+).tmp\\(.+).exe)") && contains("log.processParentName", "ieinstall.exe") && regexMatch("log.message", "(-Embedding)")', '2026-02-23 16:16:59.795356', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (978, 'Windows: UAC Bypass Attempt with IEditionUpgradeManager Elevated COM Interface', 1, 3, 2, 'Privilege Escalation', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', 'Identifies attempts to bypass User Account Control (UAC) by abusing an elevated COM Interface to launch a rogue Windows ClipUp program. Attackers may attempt to bypass UAC to stealthily execute code with elevated permissions.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1548/002/"]', 'contains("log.eventDataProcessName", "Clipup.exe") && !regexMatch("log.message", "(C:\\Windows\\System32\\ClipUp.exe)") && contains("log.eventDataParentProcessName", "dllhost.exe") && regexMatch("log.message", "(/Processid:{BD54C901-076B-434E-B6C7-17C531F4AB41)")', '2026-02-23 16:17:00.827262', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (986, 'Windows: Suspicious Cmd Execution via WMI', 1, 2, 3, 'Execution', 'T1047 - Windows Management Instrumentation', 'Identifies suspicious command execution (cmd) via Windows Management Instrumentation (WMI) on a remote host. This could be indicative of adversary lateral movement.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1047/"]', 'contains("log.eventDataParentProcessName", "WmiPrvSE.exe") && contains("log.eventDataProcessName", "cmd.exe") && contains("log.message", "\\\\127.0.0.1\\") && regexMatch("log.message", "(2>&1|1>)")', '2026-02-23 16:17:09.962165', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (987, 'Windows: Suspicious CertUtil Commands', 3, 2, 1, 'Defense Evasion', 'T1140 - Deobfuscate/Decode Files or Information', 'Identifies suspicious commands being used with certutil.exe. CertUtil is a native Windows component which is part of Certificate Services. CertUtil is often abused by attackers to live off the land for stealthier command and control or data exfiltration.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1140/"]', 'regexMatch("log.message", "(decode|encode|urlcache|verifyctl|encodehex|decodehex|exportPFX)") && contains("log.eventDataProcessName", "certutil.exe")', '2026-02-23 16:17:11.217733', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (988, 'Windows: Detection of SUNBURST command and control activity', 3, 3, 2, 'Command and Control', 'T1195 - Supply Chain Compromise', 'This rule detects post-exploitation command and control activity of the SUNBURST backdoor.', '["https://attack.mitre.org/tactics/TA0011/","https://attack.mitre.org/techniques/T1195/"]', 'regexMatch("log.eventDataProcessName", "(ConfigurationWizard.exe|NetFlowService.exe|NetflowDatabaseMaintenance.exe|SolarWinds.Administration.exe|SolarWinds.BusinessLayerHost.exe|SolarWinds.BusinessLayerHostx64.exe|SolarWinds.Collector.Service.exe|SolarwindsDiagnostics.exe)") && regexMatch("log.message", "(/swip/Upload.ashx(.+)(POST|PUT)|(POST|PUT)(.+)/swip/Upload.ashx|/swip/SystemDescription(.+)(GET|HEAD)|(GET|HEAD)(.+)/swip/SystemDescription)")', '2026-02-23 16:17:12.492026', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (989, 'Windows: Startup Persistence by a Suspicious Process', 1, 2, 3, 'Persistence', 'T1547.001 - Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder', 'Identifies files written to or modified in the startup folder by commonly abused processes. Adversaries may use this technique to maintain persistence.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1547/001/"]', 'contains("log.message", "delet") && regexMatch("log.eventDataProcessName", "(cmd.exe|powershell.exe|wmic.exe|mshta.exe|pwsh.exe|cscript.exe|wscript.exe|regsvr32.exe|RegAsm.exe|rundll32.exe|EQNEDT32.EXE|WINWORD.EXE|EXCEL.EXE|POWERPNT.EXE|MSPUB.EXE|MSACCESS.EXE|iexplore.exe|InstallUtil.exe)") && regexMatch("log.message", "(C:\\Users\\(.+)\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\|C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp\\)") && !contains("target.domain", "NT AUTHORITY")', '2026-02-23 16:17:13.507086', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (990, 'Windows: User account exposed to Kerberoasting', 3, 3, 2, 'Credential Access', 'T1558.003 - Steal or Forge Kerberos Tickets: Kerberoasting', 'Detects when a user account has the servicePrincipalName attribute modified. Attackers can abuse write privileges over a user to configure Service Principle Names (SPNs) so that they can perform Kerberoasting. Administrators can also configure this for legitimate purposes, exposing the account to Kerberoasting.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1558/003/"]', 'regexMatch("log.action", "([Dd]irectory [Ss]ervice [Cc]hanges)") && equals("log.eventCode", 5136) && regexMatch("log.message", "(servicePrincipalName)")', '2026-02-23 16:17:14.733203', true, true, 'target', null, '[]', '["target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (991, 'Windows: SIP Provider Modification', 1, 2, 3, 'Defense Evasion', 'T1553.003 - Subvert Trust Controls: SIP and Trust Provider Hijacking', 'Identifies modifications to the registered Subject Interface Package (SIP) providers. SIP providers are used by the Windows cryptographic system to validate file signatures on the system. This may be an attempt to bypass signature validation checks or inject code into critical processes.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1553/003/"]', 'equals("log.event.type", "change") && contains("log.registryDataStrings", ".dll") && regexMatch("log.registryPath", "(HKLM\\\\SOFTWARE(\\\\WOW6432Node)?\\\\Microsoft\\\\Cryptography\\\\OID\\\\EncodingType 0\\\\CryptSIPDllPutSignedDataMsg\\\\(.+)\\\\Dll|HKLM\\\\SOFTWARE(\\\\WOW6432Node)?\\\\Microsoft\\\\Cryptography\\\\Providers\\\\Trust\\\\FinalPolicy\\\\(.+)\\\\\\$Dll)")', '2026-02-23 16:17:15.971963', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (992, 'Windows: Execution via local SxS Shared Modules', 1, 2, 3, 'Execution', 'T1129 - Shared Modules', 'Identifies the creation, change, or deletion of a DLL module within a Windows SxS local folder. Adversaries may abuse shared modules to execute malicious payloads by instructing the Windows module loader to load DLLs from arbitrary local paths.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1129/"]', 'contains("log.file.extension", "dll") && regexMatch("log.file.path", "(C:\\\\(.+)\\\\.exe.local\\\\(.+).dll)")', '2026-02-23 16:17:17.159441', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (993, 'Windows: Potential Shadow Credentials added to AD Object', 3, 3, 2, 'Credential Access', 'T1556 - Modify Authentication Process', 'Identify the modification of the msDS-KeyCredentialLink attribute in an Active Directory Computer or User Object. Attackers can abuse control over the object and create a key pair, append to raw public key in the attribute, and obtain persistent and stealthy access to the target user or computer object.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1556/"]', 'regexMatch("log.action", "([Dd]irectory [Ss]ervice [Cc]hanges)") && equals("log.eventCode", 5136) && regexMatch("log.message", "(msDS-KeyCredentialLink)") && contains("log.message", ":828")', '2026-02-23 16:17:18.355353', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (994, 'Windows: Detection of a suspicious PowerShell Script with screenshot capabilities', 3, 2, 1, 'Collection', 'T1113 - Screen Capture', 'Detects PowerShell scripts that can take screenshots, which is a common feature in post-exploitation kits and remote access tools', '["https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1113/"]', 'regexMatch("log.message", "(CopyFromScreen(.+)Drawing.Bitmap|Drawing.Bitmap(.+)CopyFromScreen)")', '2026-02-23 16:17:19.371469', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (995, 'Windows: Privilege Escalation via Windir Environment Variable', 2, 3, 2, 'Privilege Escalation', 'T1574.007 - Hijack Execution Flow: Path Interception by PATH Environment Variable', 'Identifies a privilege escalation attempt via a rogue Windows directory (Windir) environment variable. This is a known primitive that is often combined with other vulnerabilities to elevate privileges.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1574/007/"]', 'regexMatch("log.message", "(C:\\windows|%SystemRoot%)") && regexMatch("log.message", "(HKEY_USERS\\(.+)\\Environment\\windir|HKEY_USERS\\(.+)\\Environment\\systemroot)")', '2026-02-23 16:17:20.577929', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (996, 'Windows: Possible ransomware attack detected. Unusual File Extensions.', 3, 3, 3, 'Impact', 'T1486 - Data Encrypted for Impact', 'Ransomware, is a type of malware that prevents users from accessing their system or personal files and requires payment of a ransom in order to gain access to them again. Identifies ransomware attempts. Files with unusual file extensions have been detected, potentially indicating encrypted files created by ransomware.', '["https://attack.mitre.org/tactics/TA0040/"]', 'equals("log.eventCode", 4663) && regexMatch("log.eventDataFileName", "\\.(7z\\.encrypted|aaa|abcd|abtc|acc|aes256|aes_ni|aes256ctr|aes256encrypted|aes_gcm|ajp|alcatraz_locked|alfa|amnesia[1-9]?|amsi|apocalypse666|armadillo|arrow|asasin|asi|atom128|auditor|aurora|autoit|avastvirusinfo|avenger|av666|azero|barak|barrax|bart|beef|beetle|bip|bit|bitcoin|bl2r|blackblock|blackmail|blast|blind|bmw|boot|braincrypt[3-8]?|broken|btcware|budak|bull|buydecryptor|cakl|calipso|calum|carote|cats|cbf|cccmn|ccrypt|cerber[3-9]?|chifrator|chimera|ciphered|crypted|clover|cobra|codnat3|combo|comrade|conficker|coot|cpt|crash|crimson|cry|crypt\\d{3,4}|crypt38|crypt72|crypt888|cryptinfinite|cryptolocker|cryptowall|cryptxxx|crypz|csfr|csone|ctb[1-4]|ctb-locker|ctrsa|cryptowin|cube|dcrpt|ddtf|dr2|dragon|dried|druid|ducky|ecrpt|eeta|etr|ee|f[0-9]{3,4}|flock|grt|grt[0-9]+|grtlock|gwz|h3ll|hades|hakunamatata|hallucinating|happy|harmful|harrow|havoc|headdesk|helpdecrypt|hermes|hidden|hideous|hijack|hilda|hitler|hjg|hmpl|hrosas|hsdf|hushed|hwrm|ihsdj|ikarus|ikasir|ikayed|ill|imbtc|img|encrypted|improved|indrik|injected|innocent|insane|interesante|jungle|kaos|karl|katana|kimcilware|kin|kiratos|kiss|kjh|locked|locky|lokf|losers|lukitus|m3g4c0rtx|m4n1fest0|m4s4g3|maas|madmax|mafi|magic|maktub|malware|manamecrypt|mandelbrot|manic|matrix|max|md5|medusa|mega|melme|merry|mesmerize|metropolitan|mikey|mikibackup|milarepa.lotos@aol.com|mirror|mmnn|mole|monro|mosk|muslat|n1n1n1|nabr|napoleon|narrow|nasoh|nataniel|neitrino|neras|nlah|nosu|novasof|nozelesn|nuclear|nwa|nymaim|obelisk|off|offwhite|ogdo|omega|omerta|onion|ooss|opencode|openme|opqz|osiris|otx|p3rf0rm4|pabluk|pack14|packagetrackr@india.com|packrat|pahd|panda|pandemic|pandora|pansy|paradise|paris|paym3|paymer|payms|pcap|pclock|peet|pelikan|penis|petya|pewcrypt|phoenix|photominr|phobos|phps|pirated|pluto|po1|point|poop|potato|pr0tect|preppy|princesa|princess|prosper|prosperity|prq|pshy|pumas|pumax|pure|purple|purpler|pwnd|pysa|q9q9|qbtex|qiuu|qkkd|qscx|qtyop|quimera|r2d2|r5a|rabit|radman|raid10|rainbow|rakhni|rambo|ramses|rat|rcrypted|react|reactor|realtek|reaper|redlion|redmat|redrum|rekt|remk|removal|remsec|remy|renaming|revenge|rezuc|rhino|ribd|rich|rip|rire|rizonesoft@protonmail.ch|rk|rmdir|robinhood|rocke|rogue|roldat|rolin|ronzware|rosenquist|rotten|roza|rpcminer|rsalive|rumba|run|rxx|uak|udjvu|unlock92|unlckr|upd|urcp|usam|usbc|v8|vag|vandt|varasto|vault|vauw|vb|ve|vendetta|venom|veracrypt|versiegelt|veton|vhd|vindows|violate|virus|vivin|vk_677|vma|vmx|volcano|vorasto|vorphal|vos|vscrypt|vxl|w4b|wakanda|wannacash|wannacry|wanted|war|wasted|wcry|weapologize|webmafia|weird|weui|whatthefuck|whistler|white|whitenoise|whiterabbit|whorus|why!decryptor|wicked|wildfire|windows10|windows7|windows8|windowsupdate|winlock|wipe|wisconsin|wizard|wlu|woolger|worm|wormfubuki|wow|wpencrypt|wq!decryptor|wrui|wtg|x1881|x3m|xampug|xdata|xencrypt|xfiles|xhelper|xlr|xman|xmd|xmd|xtbl|xtbl|xtr|xtt|xtz|xyz|yakuza|yatron|ybn|year|yellow|yheq|yty|yuke|yxo|yyto|z3|zatrov|zax|zbot|zbt|zbt|zeppelin|zerber|zet|zet|zfj|zfj|zimbra|zip|zix|zlz|zobm|zoh|zorro|zphs)$")', '2026-02-23 16:17:21.651801', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":"4663"},{"field":"origin.user","operator":"filter_term","value":"{{origin.user}}"}],"or":null,"within":"now-5m","count":20}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (997, 'Windows: Persistence via PowerShell profile', 2, 3, 1, 'Persistence', 'T1546.013 - Event Triggered Execution: PowerShell Profile', 'Identifies the creation or modification of a PowerShell profile. PowerShell profile is a script that is executed when PowerShell starts to customize the user environment, which can be abused by attackers to persist in a environment where PowerShell is common.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1098/002/"]', 'regexMatch("log.eventDataProcessName", "(:\\Users\\(.+)\\Documents\\WindowsPowerShell\\|:\\Users\\(.+)\\Documents\\PowerShell\\|:\\Windows\\System32\\WindowsPowerShell\\)") && regexMatch("log.eventDataProcessName", "(profile.ps1|Microsoft.Powershell_profile.ps1)")', '2026-02-23 16:17:22.748939', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (998, 'Windows: New ActiveSyncAllowedDeviceID Added via PowerShell', 3, 2, 1, 'Persistence', 'T1098.002 - Account Manipulation: Additional Email Delegate Permissions', 'Identifies the use of the Exchange PowerShell cmdlet, Set-CASMailbox, to add a new ActiveSync allowed device. Adversaries may target user email to collect sensitive information.', '["https://attack.mitre.org/tactics/TA0003/","https://attack.mitre.org/techniques/T1098/002/"]', 'oneOf("log.eventDataProcessName", ["powershell.exe", "pwsh.exe", "powershell_ise.exe"]) && regexMatch("log.message", "(Set-CASMailbox(.+)ActiveSyncAllowedDeviceIDs)")', '2026-02-23 16:17:23.881249', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (999, 'Windows: Potential Credential Access via DuplicateHandle in LSASS', 3, 1, 2, 'Credential Access', 'T1003 - OS Credential Dumping', 'Identifies suspicious access to an LSASS handle via DuplicateHandle. This may indicate an attempt to bypass the NtOpenProcess API to evade detection and dump LSASS memory for credential access.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/"]', 'equals("log.eventCode", 10) && contains("log.eventDataProcessName", "lsass.exe") && equals("log.eventDataGrantedAccess", "0x40") && regexMatch("log.eventDataCallTrace", "(UNKNOWN)")', '2026-02-23 16:17:25.034075', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1000, 'Windows: Potential DNS Tunneling via NsLookup', 3, 2, 1, 'Command and Control', 'T1071 - Application Layer Protocol', 'This rule identifies a large number of nslookup.exe executions with an explicit query type from the same host. This may indicate command and control activity utilizing the DNS protocol.', '["https://attack.mitre.org/tactics/TA0011/","https://attack.mitre.org/techniques/T1071/"]', 'contains("log.eventDataProcessName", "nslookup.exe") && regexMatch("log.message", "(-querytype=|-qt=|-q=|-type=)")', '2026-02-23 16:17:26.125392', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1001, 'Windows: Printer driver failed to load, possible remote code execution using PrinterNightmare exploit: CVE-2021-34527', 3, 2, 1, 'Lateral Movement', 'T1210 - Exploitation of Remote Services', 'Adversaries may exploit remote services to gain unauthorized access to internal systems once inside of a network. Exploitation of a software vulnerability occurs when an adversary takes advantage of a programming error in a program, service, or within the operating system software or kernel itself to execute adversary-controlled code. A common goal for post-compromise exploitation of remote services is for lateral movement to enable access to a remote system.', '["https://attack.mitre.org/techniques/T1210/"]', 'equals("log.eventCode", 808) && oneOf("log.severityLabel", ["Error", "error"])', '2026-02-23 16:17:27.300525', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1002, 'Windows: Possible addition of new item to Windows startup registry', 2, 3, 1, 'Persistence', 'T1547.001 - Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder', 'Adversaries may achieve persistence by adding a program to a startup folder or referencing it with a Registry run key. Adding an entry to the run keys in the Registry or startup folder will cause the program referenced to be executed when a user logs in. These programs will be executed under the context of the user and will have the account''s associated permissions level.', '["https://attack.mitre.org/techniques/T1547/001/"]', 'regexMatch("log.message", "(SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run)")', '2026-02-23 16:17:28.355757', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1003, 'Windows: PowerShell Script with Token Impersonation Capabilities', 3, 3, 2, 'Privilege Escalation', 'Token Impersonation/Theft', 'Detects scripts that contain PowerShell functions, structures, or Windows API functions related to token impersonation/theft. Attackers may duplicate then impersonate another user''s token to escalate privileges and bypass access controls.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1134/001/"]', e'oneOf("log.message", ["Invoke-TokenManipulation", "ImpersonateNamedPipeClient", "NtImpersonateThread"]) || +(regexMatch("log.message", "(UpdateProcThreadAttribute(.+)STARTUPINFOEX|STARTUPINFOEX(.+)UpdateProcThreadAttribute)") && +regexMatch("log.message", "(AdjustTokenPrivileges(.+)SeDebugPrivilege|SeDebugPrivilege(.+)AdjustTokenPrivileges)")) || +regexMatch("log.message", "((SetThreadToken|ImpersonateLoggedOnUser|CreateProcessWithTokenW|CreatePRocessAsUserW|CreateProcessAsUserA)(.+)(DuplicateToken|DuplicateTokenEx)|(DuplicateToken|DuplicateTokenEx)(.+)(SetThreadToken|ImpersonateLoggedOnUser|CreateProcessWithTokenW|CreatePRocessAsUserW|CreateProcessAsUserA))") +', '2026-02-23 16:17:29.491501', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1004, 'Windows: PowerShell Suspicious Discovery Related Windows API Functions', 2, 1, 3, 'Discovery', 'T1069 - Permission Groups Discovery', 'This rule detects the use of discovery-related Windows API functions in PowerShell Scripts. Attackers can use these functions to perform various situational awareness related activities, like enumerating users, shares, sessions, domain trusts, groups, etc.', '["https://attack.mitre.org/tactics/TA0007/","https://attack.mitre.org/techniques/T1069/"]', e'contains("log.message", ["NetShareEnum", "NetWkstaUserEnum", "NetSessionEnum", "NetLocalGroupEnum", "NetLocalGroupGetMembers", "DsGetSiteName", "DsEnumerateDomainTrusts", "WTSEnumerateSessionsEx", "WTSQuerySessionInformation", "LsaGetLogonSessionData", "QueryServiceObjectSecurity"]) +', '2026-02-23 16:17:30.600742', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1005, 'Windows: PowerShell Kerberos Ticket Request', 3, 2, 1, 'Credential Access', 'T1059 - Command and Scripting Interpreter', 'Detects PowerShell scripts that have the capability of requesting kerberos tickets, which is a common step in Kerberoasting toolkits to crack service accounts.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1059/"]', 'regexMatch("log.message", "(KerberosRequestorSecurityToken)")', '2026-02-23 16:17:31.704687', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1006, 'Windows: PowerShell PSReflect Script', 2, 3, 1, 'Execution', 'T1059 - Command and Scripting Interpreter', 'Detects the use of PSReflect in PowerShell scripts. Attackers leverage PSReflect as a library that enables PowerShell to access win32 API functions.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1059/"]', 'regexMatch("log.message", "(New-InMemoryModule|Add-Win32Type|psenum|DefineDynamicAssembly|DefineDynamicModule|Reflection.TypeAttributes|Reflection.Emit.OpCodes|Reflection.Emit.CustomAttributeBuilder|Runtime.InteropServices.DllImportAttribute)")', '2026-02-23 16:17:32.923911', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1007, 'Windows: Potential Process Injection via PowerShell', 3, 3, 2, 'Defense Evasion', 'T1055 - Process Injection', 'Detects the use of Windows API functions that are commonly abused by malware and security tools to load malicious code or inject it into remote processes.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1055/"]', 'regexMatch("log.message", "(WriteProcessMemory|CreateRemoteThread|NtCreateThreadEx|CreateThread|QueueUserAPC|SuspendThread|ResumeThread|GetDelegateForFunctionPointer)") && regexMatch("log.message", "(VirtualAlloc|VirtualAllocEx|VirtualProtect|LdrLoadDll|LoadLibrary|LoadLibraryA|LoadLibraryEx|GetProcAddress|OpenProcess|OpenProcessToken|AdjustTokenPrivileges)")', '2026-02-23 16:17:33.882169', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1008, 'Windows: Suspicious Portable Executable Encoded in Powershell Script', 2, 3, 1, 'Execution', 'T1059 - Command and Scripting Interpreter', 'Detects the presence of a portable executable (PE) in a PowerShell script by looking for its encoded header. Attackers embed PEs into PowerShell scripts to inject them into memory, avoiding defences by not writing to disk.', '["https://attack.mitre.org/tactics/TA0002/","https://attack.mitre.org/techniques/T1059/"]', 'contains("log.message", "TVqQAAMAAAAEAAAA")', '2026-02-23 16:17:34.931900', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1009, 'Windows: PowerShell MiniDump Script', 3, 3, 2, 'Credential Access', 'T1059.001 - Command and Scripting Interpreter: PowerShell', 'This rule detects PowerShell scripts capable of dumping process memory using WindowsErrorReporting or Dbghelp.dll MiniDumpWriteDump. Attackers can use this tooling to dump LSASS and get access to credentials.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1059/001/"]', 'regexMatch("log.message", "(MiniDumpWriteDump|MiniDumpWithFullMemory|pmuDetirWpmuDiniM)")', '2026-02-23 16:17:36.075967', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1010, 'Windows: PowerShell Share Enumeration Script', 3, 2, 1, 'Discovery', 'T1135 - Network Share Discovery', 'Detects scripts that contain PowerShell functions, structures, or Windows API functions related to windows share enumeration activities. Attackers, mainly ransomware groups, commonly identify and inspect network shares, looking for critical information for encryption and/or exfiltration.', '["https://attack.mitre.org/tactics/TA0007/","https://attack.mitre.org/techniques/T1135/"]', 'regexMatch("log.message", "(Invoke-ShareFinder|Invoke-ShareFinderThreaded|(shi1_netname(.+)shi1_remark)|shi1_remark(.+)shi1_netname|(NetShareEnum(.+)NetApiBufferFree)|(NetApiBufferFree(.+)NetShareEnum))")', '2026-02-23 16:17:37.216444', true, true, 'origin', '["adversary.user","adversary.ip"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1011, 'Windows: PowerShell Suspicious Payload Encoded and Compressed', 2, 3, 1, 'Defense Evasion', 'T1027 - Obfuscated Files or Information', 'Identifies the use of .NET functionality for decompression and base64 decoding combined in PowerShell scripts, which malware and security tools heavily use to deobfuscate payloads and load them directly in memory to bypass defenses.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1027/"]', 'regexMatch("log.message", "(FromBase64String)") && regexMatch("log.message", "(System.IO.Compression.DeflateStream|System.IO.Compression.GzipStream|IO.Compression.DeflateStream|IO.Compression.GzipStream)")', '2026-02-23 16:17:38.251105', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1012, 'Windows: Suspicious .NET Reflection via PowerShell', 3, 2, 1, 'Defense Evasion', 'T1055 - Process Injection', 'Detects the use of Reflection.Assembly to load PEs and DLLs in memory in PowerShell scripts. Attackers use this method to load executables and DLLs without writing to the disk, bypassing security solutions.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1055/"]', 'regexMatch("log.message", "(\\[Reflection.Assembly\\]::Load)")', '2026-02-23 16:17:39.236449', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1013, 'Windows: Outlook add-in was loaded by powershell, possible use for email collection', 3, 2, 1, 'Execution', 'T1114.001 - Email Collection: Local Email Collection', 'Adversaries may target user email on local systems to collect sensitive information. Files containing email data can be acquired from a user is local system, such as Outlook storage or cache files.', '["https://attack.mitre.org/techniques/T1114/001/"]', 'contains("log.message", "Microsoft.Office.Interop.Outlook")', '2026-02-23 16:17:40.238844', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1014, 'Windows: Multiple TS Gateway login failures', 3, 3, 2, 'Credential Access', 'T1110 - Brute Force', 'Adversaries may use brute force techniques to gain access to accounts when passwords are unknown or when password hashes are obtained.', '["https://attack.mitre.org/techniques/T1110/"]', 'equals("log.eventCode", 1001) && contains("log.message", "Microsoft-Windows-TerminalServices")', '2026-02-23 16:17:41.284851', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{origin.ip}}"}],"or":null,"within":"now-15m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1015, 'Windows: Multiple remote access login failures', 3, 2, 2, 'Credential Access', 'T1110 - Brute Force', 'Adversaries may use brute force techniques to gain access to accounts when passwords are unknown or when password hashes are obtained.', '["https://attack.mitre.org/techniques/T1110/"]', e'oneOf("log.eventCode", [20187, 20014, 20078, 20050, 20049, 2018]) && +regexMatch("log.message", "(?i)(authentication failed|login failed|access denied|authentication error|invalid credentials)") +', '2026-02-23 16:17:42.344133', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.ip","operator":"filter_term","value":"{{origin.ip}}"}],"or":null,"within":"now-15m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1016, 'Windows: Potential Invoke-Mimikatz PowerShell Script', 3, 3, 2, 'Credential Access', 'T1003.001 - OS Credential Dumping: LSASS Memory', 'Mimikatz is a credential dumper capable of obtaining plaintext Windows account logins and passwords, along with many other features that make it useful for testing the security of networks. This rule detects Invoke-Mimikatz PowerShell script and alike.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1003/","https://attack.mitre.org/techniques/T1003/001/"]', 'regexMatch("log.message", "(DumpCreds(.+)DumpCerts|DumpCerts(.+)DumpCreds|sekurlsa::logonpasswords|crypto::certificates(.+)CERT_SYSTEM_STORE_LOCAL_MACHINE|CERT_SYSTEM_STORE_LOCAL_MACHINE(.+)crypto::certificates)")', '2026-02-23 16:17:43.473800', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1017, 'Windows: Kerberos Pre-authentication Disabled for User', 2, 2, 3, 'Credential Access', 'T1558.004 - Steal or Forge Kerberos Tickets: AS-REP Roasting', 'Identifies the modification of an accounts Kerberos pre-authentication options. An adversary with GenericWrite/GenericAll rights over the account can maliciously modify these settings to perform offline password cracking attacks such as AS-REP roasting', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1558/","https://attack.mitre.org/techniques/T1558/004/"]', 'regexMatch("log.message", "(('')?[Dd]on('')?t [Rr]equire [Pp]reauth('')?(\\s)?(-)?(\\s)?[Ee]nabled)") && equals("log.eventCode", 4738)', '2026-02-23 16:17:44.457366', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1018, 'Windows: Multiple Logon Failure Followed by Logon Success', 2, 2, 3, 'Credential Access', 'T1110 - Brute Force', 'This rule is triggered when a sequence of multiple failed login attempts followed immediately by a successful login from the same IP address or source is detected. This unusual sequence of events may indicate a possible unauthorized access attempt using a brute force or password guessing technique. The purpose of this rule is to identify suspicious patterns of login activity and alert you to potential unauthorized access attempts.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1110/"]', 'equals("log.eventCode", 4624)', '2026-02-23 16:17:45.550673', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":4625},{"field":"target.user","operator":"filter_term","value":"{{target.user}}"}],"or":null,"within":"now-5m","count":10}]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1050, 'RDP Brute Force Attack', 3, 2, 2, 'Credential Access', 'T1110.001 - Brute Force: Password Guessing', e'Detects multiple failed RDP login attempts from the same source IP address, indicating a potential brute force attack. This rule monitors Windows Event ID 4625 (failed logon) with focus on network logon types (type 3) which are commonly used for RDP connections. The rule triggers when 10 or more failed attempts occur from the same IP within 15 minutes. + +Next Steps: +1. Investigate the source IP address for malicious indicators and geolocation +2. Check if the targeted user accounts are legitimate and active +3. Review successful logons from the same IP after failed attempts +4. Implement IP blocking or rate limiting for the source address +5. Enable account lockout policies if not already configured +6. Consider implementing multi-factor authentication for RDP access +7. Review RDP access logs for any successful connections during the attack timeframe +', '["https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4625","https://attack.mitre.org/techniques/T1110/001/"]', 'equals("log.eventCode", "4625") && equals("log.eventDataLogonType", "3") && exists("log.eventDataIpAddress") && !equals("log.eventDataIpAddress", "-") && !equals("log.eventDataIpAddress", "::1") && !equals("log.eventDataIpAddress", "127.0.0.1")', '2026-02-23 16:19:04.956285', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{log.eventDataIpAddress}}"},{"field":"log.eventCode","operator":"filter_term","value":"4625"},{"field":"log.eventDataLogonType","operator":"filter_term","value":"3"}],"or":null,"within":"now-15m","count":10}]', '["adversary.ip","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1051, 'PsExec Lateral Movement Detection', 3, 3, 2, 'Lateral Movement', 'T1569.002 - System Services: Service Execution', e'Detects PsExec-style lateral movement by monitoring for the creation of the PSEXESVC service +(Event ID 7045), PsExec named pipe creation, and process execution patterns characteristic +of PsExec and its variants (Impacket PSExec, Metasploit PSExec). PsExec is the most common +tool for lateral movement in enterprise environments. + +Next Steps: +1. Identify the source host that initiated the PsExec connection +2. Verify if this is authorized administrative activity +3. Check what command was executed via PsExec on the target host +4. Review the service account credentials used for PsExec authentication +5. Search for evidence of PsExec usage across other hosts in the environment +6. Check for credential theft that may have enabled the lateral movement +7. Block PsExec at the network level if not authorized +', '["https://attack.mitre.org/techniques/T1569/002/","https://jpcertcc.github.io/ToolAnalysisResultSheet/details/PsExec.htm","https://www.sans.org/blog/psexec-and-lateral-movement/"]', e'( + equals("log.eventCode", "7045") && + ( + regexMatch("log.eventDataServiceName", "(?i)^(PSEXESVC|psexec|PAExec|csexec|remcom)") || + regexMatch("log.eventDataServiceFileName", "(?i)(PSEXESVC|psexec|PAExec)\\\\.exe") || + (regexMatch("log.eventDataServiceFileName", "(?i)%SystemRoot%\\\\\\\\[a-zA-Z]{8}\\\\.exe") && + regexMatch("log.eventDataServiceName", "(?i)^[a-zA-Z]{8}$")) || + regexMatch("log.eventDataServiceFileName", "(?i)cmd\\\\.exe\\\\s+/c.*\\\\\\\\\\\\\\\\[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\\\\\") + ) +) || +( + equals("log.eventCode", "17") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + regexMatch("log.eventDataPipeName", "(?i)\\\\\\\\(psexesvc|paexec|csexec|remcom)") +) || +( + equals("log.eventCode", "4688") && + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\PSEXESVC\\\\.exe$") && + exists("log.eventDataParentProcessName") +) +', '2026-02-23 16:19:06.129872', true, true, 'origin', null, '[]', '["adversary.host","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1052, 'Process Injection Techniques Detection', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1055 - Process Injection', e'Detects various process injection techniques including CreateRemoteThread, SetWindowsHookEx, and other methods used by malware to inject code into legitimate processes. This rule monitors for suspicious cross-process activities targeting critical Windows processes and detects PowerShell/command line usage of injection APIs. + +Next Steps: +1. Investigate the source process and command line arguments for suspicious activity +2. Examine the target process for signs of compromise or abnormal behavior +3. Review process tree and parent-child relationships for the involved processes +4. Check for additional suspicious network connections or file system activities from the same host +5. Analyze memory dumps of target processes if available to confirm injection +6. Review system logs for privilege escalation attempts around the same timeframe +7. Correlate with threat intelligence to identify known malware families using similar techniques +8. Consider isolating the affected system if malicious activity is confirmed +', '["https://attack.mitre.org/techniques/T1055/","https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon","https://www.elastic.co/blog/ten-process-injection-techniques-technical-survey-common-and-trending-process"]', e'( + equals("log.eventCode", "8") && + ( + regexMatch("log.eventDataTargetImage", "(?i)(lsass\\\\.exe|csrss\\\\.exe|services\\\\.exe|on\\\\.exe|svchost\\\\.exe|explorer\\\\.exe)") || + ( + regexMatch("log.eventDataSourceImage", "(?i)(powershell\\\\.exe|cmd\\\\.exe|rundll32\\\\.exe|regsvr32\\\\.exe)") && + !regexMatch("log.eventDataTargetImage", "(?i)(conhost\\\\.exe)") + ) + ) +) || +( + equals("log.eventCode", "10") && + regexMatch("log.eventDataTargetImage", "(?i)(lsass\\\\.exe|csrss\\\\.exe|services\\\\.exe)") && + oneOf("log.eventDataGrantedAccess", ["0x1F0FFF", "0x1F1FFF", "0x1FFFFF", "0x1F3FFF"]) && + !regexMatch("log.eventDataSourceImage", "(?i)(taskmgr\\\\.exe|procexp\\\\.exe|procmon\\\\.exe|svchost\\\\.exe)") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataCommandLine", "(?i)VirtualAllocEx") || + regexMatch("log.eventDataCommandLine", "(?i)WriteProcessMemory") || + regexMatch("log.eventDataCommandLine", "(?i)CreateRemoteThread") || + regexMatch("log.eventDataCommandLine", "(?i)NtQueueApcThread") || + regexMatch("log.eventDataCommandLine", "(?i)SetWindowsHookEx") || + regexMatch("log.eventDataCommandLine", "(?i)RtlCreateUserThread") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + ( + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\[Kernel32\\\\]::(VirtualAllocEx|WriteProcessMemory|CreateRemoteThread)") || + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\[ntdll\\\\]::(NtQueueApcThread|RtlCreateUserThread)") || + contains("log.eventDataScriptBlockText", "Invoke-ReflectivePEInjection") || + contains("log.eventDataScriptBlockText", "Invoke-ProcessHollowing") + ) +) +', '2026-02-23 16:19:07.218671', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{origin.host}}"}],"or":null,"within":"now-15m","count":3}]', '["adversary.host","adversary.process"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1019, 'Windows: Possible Brute Force Attack', 2, 2, 3, 'Credential Access', 'T1110 - Brute Force', 'This rule is triggered when a pattern of repeated and rapid login attempts from the same IP address or source is detected. These login attempts may target specific user accounts or services in an attempt to crack passwords through automated brute force. The purpose of this rule is to identify possible malicious unauthorized access attempts and prevent a brute force attack against the system.', '["https://attack.mitre.org/tactics/TA0006/","https://attack.mitre.org/techniques/T1110/"]', 'equals("log.eventCode", 4625)', '2026-02-23 16:17:46.729364', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventCode","operator":"filter_term","value":"{{log.eventCode}}"},{"field":"target.user","operator":"filter_term","value":"{{target.user}}"}],"or":null,"within":"now-5m","count":10}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1020, 'Windows audit log was cleared', 1, 2, 3, 'Defense Evasion', 'T1070.001 - Indicator Removal: Clear Windows Event Logs', 'Detects when the Windows audit log (Security event log) has been cleared. Adversaries may clear event logs to remove evidence of an intrusion.', '["https://attack.mitre.org/techniques/T1070/001/"]', 'equals("log.eventCode", 1102)', '2026-02-23 16:17:47.943411', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1021, 'Windows Defender: Protection Disabled', 1, 2, 3, 'Defense Evasion', 'T1562 - Impair Defenses', 'This rule is triggered when it detects that Windows Defender protection has been turned off or disabled on the system. The alert is crucial to identify any unauthorized or malicious actions that may leave the system vulnerable to security threats.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1562/"]', 'oneOf("log.eventCode", [5001, 5012])', '2026-02-23 16:17:49.046642', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1022, 'Windows: An attempt was made to set the Directory Services Restore Mode', 1, 2, 3, 'Collection', 'T1005 - Data from Local System', 'This event generates on every attempt made to set the Directory Services Restore Mode. Is a function on Active Directory Domain Controllers to take the server offline for emergency maintenance, particularly restoring backups of AD objects. It is accessed on Windows Server via the advanced startup menu, similarly to safe mode.', '["https://attack.mitre.org/tactics/TA0009/","https://attack.mitre.org/techniques/T1005"]', 'equals("log.eventCode", 4794)', '2026-02-23 16:17:50.216828', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1023, 'Windows: Hiding files with attrib.exe', 1, 2, 3, 'Defense Evasion', 'T1564 - Hide Artifacts', 'This correlation rule detects events where the attrib.exe tool is used to hide files on the system. Cloaking files using this tool can be an indicator of suspicious or malicious activity, as it could be used by malicious actors to evade detection and hide their presence on the system.', '["https://attack.mitre.org/tactics/TA0005/","https://attack.mitre.org/techniques/T1564/"]', 'regexMatch("log.message", "(attrib +h|attrib.exe +h)")', '2026-02-23 16:17:51.320773', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1024, 'Windows: Changes to AdminSDHolder', 1, 2, 3, 'Privilege Escalation', 'T1087 - Account Discovery', 'This rule detects changes to the AdminSDHolder object within the Active Directory. The AdminSDHolder object is critical to maintaining the ACLs of highly privileged accounts and groups in the domain. Any unexpected modification to this object could indicate a privilege escalation attempt or malicious action.', '["https://attack.mitre.org/tactics/TA0004/","https://attack.mitre.org/techniques/T1069/"]', 'regexMatch("log.action", "Directory Service Changes") && equals("log.eventCode", 5136) && regexMatch("log.eventDataObjectName", "AdminSDHolder")', '2026-02-23 16:17:52.517738', true, true, 'origin', null, '[]', '["adversary.ip","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1066, 'Malicious PowerShell Execution', 3, 3, 2, 'Execution', 'T1059.001 - Command and Scripting Interpreter: PowerShell', e'Detects malicious PowerShell execution patterns including encoded commands, download cradles, +AMSI bypass attempts, execution policy bypasses, and common offensive PowerShell techniques. +These patterns are frequently used by attackers for initial access, lateral movement, and +payload delivery. + +Next Steps: +1. Isolate the affected host immediately +2. Decode any Base64-encoded command content for analysis +3. Check parent process - unexpected parents (e.g., Word, Excel) indicate macro-based attacks +4. Review network connections for download cradle destinations +5. Examine PowerShell transcription and script block logs for full command content +6. Search for persistence mechanisms created by the script +7. Check if AMSI was successfully bypassed and what payload was executed +8. Correlate with any recent phishing emails received by the user +', '["https://attack.mitre.org/techniques/T1059/001/","https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging","https://www.fireeye.com/blog/threat-research/2016/02/greater_visibility_t.html"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataNewProcessName", "(?i)powershell\\\\.exe$|pwsh\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)powershell\\\\.exe$|pwsh\\\\.exe$") + ) && + ( + regexMatch("log.eventDataCommandLine", "(?i)-[eE][nN][cC]\\\\s+[A-Za-z0-9+/=]{50,}") || + regexMatch("log.eventDataCommandLine", "(?i)(-nop|-noni|-w\\\\s+hidden|-ep\\\\s+bypass|-exec\\\\s+bypass)") || + regexMatch("log.eventDataCommandLine", "(?i)(DownloadString|DownloadFile|DownloadData|WebClient|Invoke-WebRequest|wget|curl|Start-BitsTransfer)") || + regexMatch("log.eventDataCommandLine", "(?i)(Invoke-Expression|IEX|Invoke-Command)\\\\s*[\\\\(\\\\{]") || + regexMatch("log.eventDataCommandLine", "(?i)(Net\\\\.WebClient|IO\\\\.MemoryStream|IO\\\\.StreamReader|IO\\\\.Compression)") || + regexMatch("log.eventDataCommandLine", "(?i)(FromBase64String|ToBase64String|\\\\[Convert\\\\]::)") || + regexMatch("log.eventDataCommandLine", "(?i)-[eE][nN][cC]\\\\s+[A-Za-z0-9+/=]{50,}") || + regexMatch("log.eventDataCommandLine", "(?i)(-nop|-noni|-w\\\\s+hidden|-ep\\\\s+bypass|-exec\\\\s+bypass)") || + regexMatch("log.eventDataCommandLine", "(?i)(DownloadString|DownloadFile|DownloadData|WebClient|Invoke-WebRequest)") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + contains("log.eventDataScriptBlockText", "AmsiInitFailed") || + contains("log.eventDataScriptBlockText", "amsiContext") || + contains("log.eventDataScriptBlockText", "AmsiUtils") || + contains("log.eventDataScriptBlockText", "amsi.dll") || + regexMatch("log.eventDataScriptBlockText", "(?i)(Invoke-Obfuscation|Invoke-CradleCrafter|Out-EncodedCommand)") || + regexMatch("log.eventDataScriptBlockText", "(?i)(New-Object\\\\s+Net\\\\.Sockets\\\\.TCPClient|Net\\\\.WebClient\\\\)?\\\\.DownloadString)") || + regexMatch("log.eventDataScriptBlockText", "(?i)(\\\\$env:COMSPEC|cmd\\\\.exe.*/c)") || + regexMatch("log.eventDataScriptBlockText", "(?i)(Add-Type.*DllImport|\\\\[DllImport)") || + contains("log.eventDataScriptBlockText", "Reflection.Assembly") || + contains("log.eventDataScriptBlockText", "Invoke-Shellcode") || + contains("log.eventDataScriptBlockText", "AmsiInitFailed") || + contains("log.eventDataScriptBlockText", "AmsiUtils") || + regexMatch("log.eventDataScriptBlockText", "(?i)(Invoke-Obfuscation|Invoke-CradleCrafter|Out-EncodedCommand)") + ) +) +', '2026-02-23 16:19:22.800322', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{origin.host}}"}],"or":null,"within":"now-10m","count":2}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1067, 'LSASS Memory Dump Alternatives', 3, 3, 1, 'Credential Access', 'T1003.001 - OS Credential Dumping: LSASS Memory', e'Detects alternatives to Mimikatz for dumping LSASS process memory, including procdump.exe, +comsvcs.dll MiniDump via rundll32, Task Manager LSASS dump, and direct process access to LSASS. +These tools are commonly used by attackers who avoid Mimikatz to extract credentials from memory. + +Next Steps: +1. Isolate the affected host immediately to prevent lateral movement +2. Identify the user account and parent process that initiated the dump +3. Check if procdump or comsvcs.dll was used and from what directory +4. Review for any exfiltration of the dump file to external destinations +5. Reset all credentials that were potentially exposed on the affected host +6. Investigate how the attacker obtained the privileges needed for LSASS access +7. Search for evidence of credential reuse across the environment +', '["https://attack.mitre.org/techniques/T1003/001/","https://www.microsoft.com/en-us/security/blog/2022/10/05/detecting-and-preventing-lsass-credential-dumping-attacks/","https://redcanary.com/threat-detection-report/techniques/lsass-memory/"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + (regexMatch("log.eventDataCommandLine", "(?i)procdump.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)procdump.*lsass")) || + (regexMatch("log.eventDataCommandLine", "(?i)rundll32.*comsvcs\\\\.dll.*MiniDump") || + regexMatch("log.eventDataCommandLine", "(?i)rundll32.*comsvcs\\\\.dll.*MiniDump")) || + (regexMatch("log.eventDataCommandLine", "(?i)Out-Minidump.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)Out-Minidump.*lsass")) || + (regexMatch("log.eventDataCommandLine", "(?i)taskmgr.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)taskmgr.*lsass")) || + (regexMatch("log.eventDataCommandLine", "(?i)sqldumper\\\\.exe.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)sqldumper\\\\.exe.*lsass")) || + (regexMatch("log.eventDataCommandLine", "(?i)createdump\\\\.exe.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)createdump\\\\.exe.*lsass")) || + (regexMatch("log.eventDataCommandLine", "(?i)rdrleakdiag\\\\.exe.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)rdrleakdiag\\\\.exe.*lsass")) + ) +) || +( + equals("log.eventCode", "10") && + regexMatch("log.eventDataTargetImage", "(?i)lsass\\\\.exe$") && + oneOf("log.eventDataGrantedAccess", ["0x1010", "0x1038", "0x1fffff", "0x1410", "0x143a"]) +) +', '2026-02-23 16:19:23.774229', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1025, 'WMI Remote Command Execution Detection', 3, 3, 2, 'Lateral Movement', 'T1047 - Windows Management Instrumentation', e'Detects WMI-based remote command execution using wmic.exe /node: parameter or PowerShell +Invoke-WmiMethod / Invoke-CimMethod for lateral movement. Attackers use WMI to execute +commands on remote systems without dropping files to disk, making it a stealthy lateral +movement technique that leverages built-in Windows functionality. + +Next Steps: +1. Identify the target host specified in the /node: parameter +2. Verify if this is authorized administrative WMI usage +3. Check what process or command was executed on the remote host +4. Review the credentials used for the WMI connection +5. Search for WMI execution evidence on the target host +6. Check for additional lateral movement from the same source +7. Monitor for process creation events on the target system +', '["https://attack.mitre.org/techniques/T1047/","https://www.fireeye.com/content/dam/fireeye-www/global/en/current-threats/pdfs/wp-windows-management-instrumentation.pdf","https://www.sans.org/blog/wmi-for-detection-and-response/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + (regexMatch("log.eventDataCommandLine", "(?i)wmic(.exe)?\\\\s+/node:") || + regexMatch("log.eventDataCommandLine", "(?i)wmic(.exe)?\\\\s+/node:")) || + (regexMatch("log.eventDataCommandLine", "(?i)Invoke-WmiMethod.*-ComputerName") || + regexMatch("log.eventDataCommandLine", "(?i)Invoke-WmiMethod.*-ComputerName")) || + (regexMatch("log.eventDataCommandLine", "(?i)Invoke-CimMethod.*-ComputerName") || + regexMatch("log.eventDataCommandLine", "(?i)Invoke-CimMethod.*-ComputerName")) || + (regexMatch("log.eventDataCommandLine", "(?i)Get-WmiObject.*-ComputerName.*Win32_Process") || + regexMatch("log.eventDataCommandLine", "(?i)Get-WmiObject.*-ComputerName.*Win32_Process")) || + (regexMatch("log.eventDataCommandLine", "(?i)wmic(.exe)?.*process\\\\s+call\\\\s+create") || + regexMatch("log.eventDataCommandLine", "(?i)wmic(.exe)?.*process\\\\s+call\\\\s+create")) +) +', '2026-02-23 16:18:36.664953', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1026, 'WMI Event Subscription Persistence Detection', 2, 3, 2, 'Persistence', 'T1546.003 - Event Triggered Execution: Windows Management Instrumentation Event Subscription', e'Detects creation of WMI event subscriptions used for stealthy persistence. Attackers create +WMI event filters and consumers that execute commands when specific system events occur. +This is a highly effective persistence mechanism because it survives reboots and is difficult +to detect without specific monitoring. The rule monitors for wmic.exe commands creating +event subscriptions and PowerShell WMI subscription creation patterns. + +Next Steps: +1. Enumerate all WMI event subscriptions using Get-WMIObject or wmic +2. Examine the event filter conditions and consumer actions +3. Identify the command or script executed by the consumer +4. Remove malicious WMI subscriptions immediately +5. Check for related persistence mechanisms on the same host +6. Review the timeline to identify the initial compromise +7. Search for similar WMI persistence across other endpoints +', '["https://attack.mitre.org/techniques/T1546/003/","https://www.fireeye.com/blog/threat-research/2016/08/wmi_vs_wmi_monitor.html","https://pentestlab.blog/2020/01/21/persistence-wmi-event-subscription/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + ( + regexMatch("log.eventDataCommandLine", "(?i)wmic.*(/namespace|__EventFilter|__EventConsumer|__FilterToConsumerBinding)") || + regexMatch("log.eventDataCommandLine", "(?i)wmic.*(/namespace|__EventFilter|__EventConsumer|__FilterToConsumerBinding)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)(Set-WmiInstance|New-CimInstance).*(__EventFilter|__EventConsumer|CommandLineEventConsumer|ActiveScriptEventConsumer)") || + regexMatch("log.eventDataCommandLine", "(?i)(Set-WmiInstance|New-CimInstance).*(__EventFilter|__EventConsumer|CommandLineEventConsumer|ActiveScriptEventConsumer)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)Register-WmiEvent") || + regexMatch("log.eventDataCommandLine", "(?i)Register-WmiEvent") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)mofcomp\\\\.exe") || + regexMatch("log.eventDataCommandLine", "(?i)mofcomp\\\\.exe") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + regexMatch("log.eventDataScriptBlockText", "(?i)(__EventFilter|__EventConsumer|__FilterToConsumerBinding)") || + regexMatch("log.eventDataScriptBlockText", "(?i)(CommandLineEventConsumer|ActiveScriptEventConsumer)") || + contains("log.eventDataScriptBlockText", "Register-WmiEvent") + ) +) +', '2026-02-23 16:18:37.849462', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1027, 'Windows Script Host Suspicious Execution', 3, 3, 2, 'Execution', 'T1059.005 - Command and Scripting Interpreter: Visual Basic', e'Detects suspicious execution of wscript.exe and cscript.exe with potentially malicious script +files or command-line arguments. Attackers commonly use Windows Script Host to execute VBScript +and JScript payloads delivered via phishing emails, drive-by downloads, or as secondary +payloads during an intrusion. The rule focuses on scripts executed from suspicious locations +and with suspicious arguments. + +Next Steps: +1. Identify the script file being executed and analyze its contents +2. Check the parent process (e.g., Outlook, Word, browser) for phishing indicators +3. Review the script file location for legitimacy +4. Analyze any network connections made by the script +5. Check if the script downloads or executes additional payloads +6. Search for the same script across other endpoints +7. Block the script and remove any persistence mechanisms it created +', '["https://attack.mitre.org/techniques/T1059/005/","https://lolbas-project.github.io/lolbas/Binaries/Wscript/","https://lolbas-project.github.io/lolbas/Binaries/Cscript/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\(wscript|cscript)\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)\\\\\\\\(wscript|cscript)\\\\.exe$") +) && +( + (regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") || + regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)")) || + (regexMatch("log.eventDataCommandLine", "(?i)//[eE]:(vbscript|jscript)") || + regexMatch("log.eventDataCommandLine", "(?i)//[eE]:(vbscript|jscript)")) || + (regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://|\\\\\\\\\\\\\\\\[0-9])") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://|\\\\\\\\\\\\\\\\[0-9])")) || + (regexMatch("log.eventDataCommandLine", "(?i)\\\\.(vbs|vbe|js|jse|wsf|wsh)\\\\s+(//B|//nologo)") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\.(vbs|vbe|js|jse|wsf|wsh)\\\\s+(//B|//nologo)")) || + (regexMatch("log.eventDataCommandLine", "(?i)(CreateObject|WScript\\\\.Shell|Scripting\\\\.FileSystemObject|ADODB\\\\.Stream)") || + regexMatch("log.eventDataCommandLine", "(?i)(CreateObject|WScript\\\\.Shell|Scripting\\\\.FileSystemObject|ADODB\\\\.Stream)")) +) +', '2026-02-23 16:18:39.016715', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1028, 'Windows Remote Management (WinRM) Abuse', 3, 3, 2, 'Lateral Movement', 'T1021.006 - Remote Services: Windows Remote Management', e'Detects potential abuse of Windows Remote Management (WinRM) for lateral movement. Monitors for successful logon events (4624) with network logon type 3 combined with privilege escalation (4672) and WinRM-related process activity, indicating remote command execution via WinRM. + +Next Steps: +1. Investigate the source IP address and verify if it\'s an authorized administrative workstation +2. Review the target user account for any signs of compromise or unusual privilege usage +3. Examine recent PowerShell execution logs and command history on the target system +4. Check for concurrent suspicious activities from the same source IP across other systems +5. Verify if the WinRM connection aligns with scheduled maintenance or authorized administrative tasks +6. Review network traffic patterns between source and target systems for data exfiltration indicators +7. Validate the legitimacy of any processes spawned through the WinRM session +8. Consider implementing additional monitoring for WinRM usage if this represents unexpected activity +', '["https://jpcertcc.github.io/ToolAnalysisResultSheet/details/WinRM.htm","https://attack.mitre.org/techniques/T1021/006/"]', 'equals("log.eventCode", "4624") && equals("log.eventDataLogonType", "3") && exists("log.eventDataProcessName") && (contains("log.eventDataProcessName", "wsmprovhost.exe") || contains("log.eventDataProcessName", "winrshost.exe") || contains("log.eventDataProcessName", "powershell.exe"))', '2026-02-23 16:18:40.204416', true, true, 'origin', null, '[]', '["lastEvent.target.user","adversary.host","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1029, 'Windows Defender Tampering Detection', 2, 3, 3, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects attempts to disable or tamper with Windows Defender components through registry modifications, service stops, or exclusion additions. This includes registry changes to disable antivirus features, stopping Windows Defender services, or using PowerShell commands to modify protection settings. + +Next Steps: +1. Investigate the user account and process responsible for the tampering attempt +2. Check if this is part of authorized maintenance or if it\'s malicious activity +3. Review recent logon events and process execution history on the affected host +4. Verify Windows Defender configuration and ensure it\'s properly restored +5. Scan the system for malware that may have disabled protection +6. Check for other security tool tampering attempts across the environment +', '["https://attack.mitre.org/techniques/T1562/001/","https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-antivirus/troubleshoot-windows-defender-antivirus"]', e'(equals("log.eventId", "4657") && + contains("log.eventDataObjectName", "\\\\Windows Defender\\\\") && + (contains("log.eventDataObjectValueName", "DisableAntiSpyware") || + contains("log.eventDataObjectValueName", "DisableAntiVirus") || + contains("log.eventDataObjectValueName", "DisableRealtimeMonitoring"))) || +(equals("log.eventId", "7040") && + contains("log.eventDataServiceName", "WinDefend") && + equals("log.eventDataNewState", "disabled")) || +(equals("log.eventId", "4688") && + contains("log.eventDataCommandLine", "Set-MpPreference") && + (contains("log.eventDataCommandLine", "DisableRealtimeMonitoring") || + contains("log.eventDataCommandLine", "DisableIOAVProtection") || + contains("log.eventDataCommandLine", "DisableBehaviorMonitoring"))) +', '2026-02-23 16:18:41.336143', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1030, 'Suspicious Admin Share Access Detection', 3, 3, 2, 'Lateral Movement', 'T1021.002 - Remote Services: SMB/Windows Admin Shares', e'Detects suspicious access to Windows administrative shares (C$, ADMIN$, IPC$) which is +commonly used for lateral movement. While admin share access can be legitimate, attackers +use these shares to copy payloads, execute remote commands, and move laterally across the +network. The rule monitors Event ID 5140 (network share access) and Event ID 5145 (detailed +share access) for admin share connections from non-standard sources. + +Next Steps: +1. Identify the source IP and user account accessing the admin share +2. Verify if this is authorized administrative access or IT operations +3. Check what files were read or written to the admin share +4. Review if tools or malware were copied via the share +5. Check for service creation or scheduled task creation on the target +6. Correlate with authentication events from the same source IP +7. Block unauthorized admin share access via Group Policy +', '["https://attack.mitre.org/techniques/T1021/002/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=5140","https://www.sans.org/blog/detecting-lateral-movement-using-windows-admin-shares/"]', e'( + equals("log.eventCode", "5140") && + equals("log.channel", "Security") && + regexMatch("log.eventDataShareName", "(?i)\\\\\\\\\\\\*(C\\\\$|ADMIN\\\\$)$") && + exists("log.eventDataIpAddress") && + !equals("log.eventDataIpAddress", "::1") && + !equals("log.eventDataIpAddress", "127.0.0.1") && + !regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE|\\\\$)") && + !regexMatch("log.eventDataSubjectUserName", "(?i)\\\\$$") +) || +( + equals("log.eventCode", "5145") && + equals("log.channel", "Security") && + regexMatch("log.eventDataShareName", "(?i)\\\\\\\\\\\\*(C\\\\$|ADMIN\\\\$)$") && + regexMatch("log.eventDataRelativeTargetName", "(?i)\\\\.(exe|dll|bat|cmd|ps1|vbs|hta)$") && + exists("log.eventDataIpAddress") +) +', '2026-02-23 16:18:42.527359', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{log.eventDataIpAddress}}"}],"or":null,"within":"now-30m","count":3}]', '["lastEvent.log.eventDataIpAddress","target.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1031, 'Vulnerable Driver Loading Detection (BYOVD)', 3, 3, 3, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', e'Detects Bring Your Own Vulnerable Driver (BYOVD) attacks where adversaries load known vulnerable +kernel drivers to disable security tools, escalate privileges, or gain kernel-level access. +This technique is increasingly used by ransomware groups and APTs to bypass endpoint protection. +The rule monitors for service installations loading known vulnerable drivers and suspicious +driver-related command patterns. + +Next Steps: +1. Identify the loaded driver and check against known vulnerable driver lists (loldrivers.io) +2. Examine the service installation that loaded the driver +3. Check if endpoint security tools were disabled after driver loading +4. Review the process that installed the vulnerable driver +5. Remove the vulnerable driver and restore security tools +6. Block the driver hash via WDAC or application control policies +7. Check for privilege escalation or security tool tampering after driver load +8. Search for the same driver across other endpoints +', '["https://attack.mitre.org/techniques/T1068/","https://www.loldrivers.io/","https://github.com/magicsword-io/LOLDrivers"]', e'( + equals("log.eventCode", "4697") && + equals("log.channel", "Security") && + ( + regexMatch("log.eventDataServiceFileName", "(?i)(dbutil_2_3|dbutildrv2|rtcore64|gdrv|asio|ene\\\\.sys|procexp|viragt64|aswarpot|hw64|cpuz|gmer64|mhyprot2|kdmapper|iqvw64e|echo_driver|winio|amifldrv64|elby|vboxdrv|gpu_temp|nicm|phymemx64|micio64|directio64|blacklotus|irec|zemana)") || + regexMatch("log.eventDataServiceFileName", "(?i)\\\\.sys\\\\s*$") + ) +) || +( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataCommandLine", "(?i)(sc\\\\.exe|sc)\\\\s+(create|start)\\\\s+.*type=\\\\s*kernel") || + regexMatch("log.eventDataCommandLine", "(?i)(sc\\\\.exe|sc)\\\\s+(create|start)\\\\s+.*type=\\\\s*kernel") || + regexMatch("log.eventDataCommandLine", "(?i)(sc\\\\.exe|sc)\\\\s+create\\\\s+.*binPath=.*\\\\.sys") || + regexMatch("log.eventDataCommandLine", "(?i)(sc\\\\.exe|sc)\\\\s+create\\\\s+.*binPath=.*\\\\.sys") + ) +) +', '2026-02-23 16:18:43.714091', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1032, 'UAC Bypass Attempt Detection', 3, 3, 2, 'Privilege Escalation, Defense Evasion', 'T1548.002 - Abuse Elevation Control Mechanism: Bypass User Account Control', e'Detects potential UAC bypass attempts by monitoring for processes with elevated privileges that were not launched through the proper UAC consent mechanism. This rule identifies processes running with elevated token types (TokenElevationTypeVirtual) without proper UAC consent flow, which may indicate an attempt to bypass User Account Control security measures. + +Next Steps: +1. Investigate the process that triggered the alert - examine the process name, command line arguments, and parent process +2. Check if the process is known legitimate software that should have elevated privileges +3. Verify the user account that launched the process and their normal privilege level +4. Look for other suspicious activities on the same host around the same time +5. Check for known UAC bypass techniques such as DLL hijacking, registry manipulation, or abuse of auto-elevated processes +6. Examine the process execution chain to identify the attack vector +7. Consider isolating the affected system if malicious activity is confirmed +8. Review security policies and UAC settings to prevent similar bypasses +', '["https://attack.mitre.org/techniques/T1548/002/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=4688"]', e'equals("log.eventId", "4688") && +equals("log.eventDataTokenElevationType", "2") && +!contains("log.eventDataParentProcessName", "consent.exe") && +!contains("log.eventDataProcessName", "TrustedInstaller.exe") && +!contains("log.eventDataSubjectUserName", "SYSTEM") && +(contains("log.eventDataParentProcessName", "fodhelper.exe") || + contains("log.eventDataParentProcessName", "computerdefaults.exe") || + contains("log.eventDataParentProcessName", "sdclt.exe") || + contains("log.eventDataParentProcessName", "slui.exe") || + contains("log.eventDataParentProcessName", "eventvwr.exe") || + contains("log.eventDataParentProcessName", "cmstp.exe") || + contains("log.eventDataParentProcessName", "msconfig.exe") || + contains("log.eventDataParentProcessName", "dccw.exe") || + (contains("log.eventDataProcessName", "cmd.exe") || contains("log.eventDataProcessName", "powershell.exe") || contains("log.eventDataProcessName", "pwsh.exe"))) +', '2026-02-23 16:18:44.818132', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1033, 'Volume Shadow Copy Deletion', 2, 3, 3, 'Impact', 'T1490 - Inhibit System Recovery', e'Detects deletion of Volume Shadow Copies which is commonly performed by ransomware to prevent recovery of encrypted files. This rule identifies various methods attackers use to delete shadow copies including vssadmin, wmic, PowerShell, wbadmin, and bcdedit commands. + +Next Steps: +1. Immediately investigate the affected host for signs of ransomware activity +2. Check for recent file encryption or unusual file modifications +3. Review process execution timeline around the shadow copy deletion +4. Verify if this was legitimate administrative activity or malicious +5. Examine network connections and lateral movement indicators +6. Check backup integrity and consider isolating the system if ransomware is confirmed +7. Review user accounts and privileges involved in the activity +', '["https://attack.mitre.org/techniques/T1490/","https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/vssadmin"]', e'equals("log.eventId", "4688") && +((endsWith("log.eventDataProcessName", "vssadmin.exe") && + (contains("log.eventDataCommandLine", "delete shadows") || + (contains("log.eventDataCommandLine", "resize shadowstorage") && + contains("log.eventDataCommandLine", "maxsize=")))) || + (endsWith("log.eventDataProcessName", "wmic.exe") && + contains("log.eventDataCommandLine", "shadowcopy") && + contains("log.eventDataCommandLine", "delete")) || + (endsWith("log.eventDataProcessName", "powershell.exe") && + (contains("log.eventDataCommandLine", "Get-WmiObject") || + contains("log.eventDataCommandLine", "gwmi")) && + contains("log.eventDataCommandLine", "Win32_ShadowCopy") && + contains("log.eventDataCommandLine", "Delete()")) || + (endsWith("log.eventDataProcessName", "wbadmin.exe") && + contains("log.eventDataCommandLine", "delete") && + (contains("log.eventDataCommandLine", "catalog") || + contains("log.eventDataCommandLine", "backup"))) || + (endsWith("log.eventDataProcessName", "bcdedit.exe") && + contains("log.eventDataCommandLine", "recoveryenabled") && + contains("log.eventDataCommandLine", "no"))) +', '2026-02-23 16:18:45.831954', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataProcessName","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1034, 'Tunneling Service C2 Detection', 3, 2, 2, 'Command and Control', 'T1572 - Protocol Tunneling', e'Detects execution of tunneling services (ngrok, cloudflared, devtunnels, localtonet, bore, chisel) +that create reverse tunnels for command and control or to expose internal services to the internet. +These tools are increasingly abused by threat actors for persistent C2 channels that bypass +network security controls because traffic appears as legitimate HTTPS connections. + +Next Steps: +1. Identify the tunneling tool being used and its configuration +2. Determine what internal service or port is being exposed +3. Check if this is authorized usage (dev/test) or malicious +4. Review the tunnel endpoint for C2 activity +5. Block the tunneling tool and its associated domains at the firewall +6. Terminate the tunnel process and remove the tool +7. Check for data exfiltration through the tunnel +8. Hunt for the same tunneling tool across other endpoints +', '["https://attack.mitre.org/techniques/T1572/","https://www.microsoft.com/en-us/security/blog/2023/05/24/volt-typhoon-targets-us-critical-infrastructure-with-living-off-the-land-techniques/","https://ngrok.com/abuse"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)(ngrok|cloudflared|devtunnels|localtonet|bore|chisel|frpc|rathole)\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)(ngrok|cloudflared|devtunnels|localtonet|bore|chisel|frpc|rathole)\\\\.exe$") || + regexMatch("log.eventDataCommandLine", "(?i)ngrok\\\\s+(http|tcp|tls|start)") || + regexMatch("log.eventDataCommandLine", "(?i)ngrok\\\\s+(http|tcp|tls|start)") || + regexMatch("log.eventDataCommandLine", "(?i)cloudflared\\\\s+tunnel\\\\s+(run|--url)") || + regexMatch("log.eventDataCommandLine", "(?i)cloudflared\\\\s+tunnel\\\\s+(run|--url)") || + regexMatch("log.eventDataCommandLine", "(?i)devtunnels\\\\s+(host|create|port)") || + regexMatch("log.eventDataCommandLine", "(?i)devtunnels\\\\s+(host|create|port)") || + regexMatch("log.eventDataCommandLine", "(?i)chisel\\\\s+(client|server)") || + regexMatch("log.eventDataCommandLine", "(?i)chisel\\\\s+(client|server)") || + regexMatch("log.eventDataCommandLine", "(?i)frpc\\\\.exe.*-c") || + regexMatch("log.eventDataCommandLine", "(?i)frpc\\\\.exe.*-c") +) +', '2026-02-23 16:18:46.920824', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1035, 'Windows Token Manipulation', 3, 3, 3, 'Defense Evasion, Privilege Escalation', 'Access Token Manipulation', 'Detects potential token manipulation attacks used for privilege escalation. Monitors for suspicious combinations of privileged service calls (4673) and operations on privileged objects (4674) along with special privilege assignments (4672) that include sensitive privileges like SeDebugPrivilege, SeImpersonatePrivilege, or SeTcbPrivilege commonly abused for token manipulation.', '["https://medium.com/palantir/windows-privilege-abuse-auditing-detection-and-defense-3078a403d74e","https://attack.mitre.org/techniques/T1134/"]', 'equals("log.eventCode", "4672") && exists("log.eventDataPrivilegeList") && (contains("log.eventDataPrivilegeList", "SeDebugPrivilege") || contains("log.eventDataPrivilegeList", "SeImpersonatePrivilege") || contains("log.eventDataPrivilegeList", "SeTcbPrivilege") || contains("log.eventDataPrivilegeList", "SeAssignPrimaryTokenPrivilege") || contains("log.eventDataPrivilegeList", "SeLoadDriverPrivilege") || contains("log.eventDataPrivilegeList", "SeRestorePrivilege"))', '2026-02-23 16:18:47.970943', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{origin.host}}"},{"field":"log.eventDataTargetLogonId","operator":"filter_term","value":"{{log.eventDataTargetLogonId}}"},{"field":"log.eventCode","operator":"should_terms","value":"4673,4674"}],"or":null,"within":"now-10m","count":3}]', '["lastEvent.log.eventDataTargetLogonId","lastEvent.target.user","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1036, 'File Timestomping Detection', 1, 3, 1, 'Defense Evasion', 'T1070.006 - Indicator Removal: Timestomp', e'Detects timestomping activities where attackers modify file creation or modification timestamps +to blend malicious files with legitimate system files. Common tools include PowerShell +Set-ItemProperty, timestomp.exe, NirCmd, and direct API calls to SetFileTime. This technique +is used to evade forensic timeline analysis and make malicious files appear older. + +Next Steps: +1. Identify which files had their timestamps modified +2. Check if the modified timestamps match legitimate system file dates (common pattern) +3. Analyze the files with modified timestamps for malicious content +4. Review the process and user that performed the timestamp modification +5. Investigate what the attacker was trying to hide with timestomping +6. Use $MFT analysis to detect original timestamps vs modified ones +7. Search for additional anti-forensic techniques on the same host +', '["https://attack.mitre.org/techniques/T1070/006/","https://www.inversecos.com/2022/04/defence-evasion-technique-timestomping.html","https://andreafortuna.org/2017/10/06/timestomping-what-it-is-and-how-to-detect-it/"]', e'( + equals("log.eventCode", "2") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + exists("log.eventDataPreviousCreationUtcTime") && + exists("log.eventDataCreationUtcTime") && + !regexMatch("log.eventDataImage", "(?i)(setup|install|update|patch|msiexec|TiWorker|TrustedInstaller|svchost)\\\\.exe$") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + regexMatch("log.eventDataCommandLine", "(?i)Set-ItemProperty.*CreationTime|Set-ItemProperty.*LastWriteTime|Set-ItemProperty.*LastAccessTime") || + regexMatch("log.eventDataCommandLine", "(?i)Set-ItemProperty.*CreationTime|Set-ItemProperty.*LastWriteTime|Set-ItemProperty.*LastAccessTime") || + regexMatch("log.eventDataCommandLine", "(?i)timestomp|SetFileTime|NirCmd.*setfiletime") || + regexMatch("log.eventDataCommandLine", "(?i)timestomp|SetFileTime|NirCmd.*setfiletime") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\[IO\\\\.File\\\\]::SetCreationTime|\\\\[IO\\\\.File\\\\]::SetLastWriteTime") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\[IO\\\\.File\\\\]::SetCreationTime|\\\\[IO\\\\.File\\\\]::SetLastWriteTime") + ) +) +', '2026-02-23 16:18:49.103144', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1037, 'Sysmon Service Tampering Detection', 3, 3, 3, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects attempts to tamper with the Sysmon monitoring service including unloading the Sysmon +driver, stopping or deleting the Sysmon service, modifying Sysmon configuration, or renaming +the Sysmon binary. Attackers target Sysmon specifically because it provides rich endpoint +telemetry that enables threat detection. Disabling Sysmon is a critical defense evasion step. + +Next Steps: +1. Immediately investigate the process and user that tampered with Sysmon +2. Restore Sysmon service and verify it is running correctly +3. Check if the Sysmon driver is still loaded in the kernel +4. Review what malicious activity occurred during the Sysmon outage +5. Re-apply the Sysmon configuration from a known good backup +6. Investigate the full attack chain that led to Sysmon tampering +7. Consider implementing tamper protection for Sysmon +', '["https://attack.mitre.org/techniques/T1562/001/","https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon","https://undev.ninja/sysmon-evasion-techniques/"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + (regexMatch("log.eventDataCommandLine", "(?i)sysmon.*-u") || + regexMatch("log.eventDataCommandLine", "(?i)sysmon.*-u")) || + (regexMatch("log.eventDataCommandLine", "(?i)sc(.exe)?\\\\s+(stop|delete|config)\\\\s+sysmon") || + regexMatch("log.eventDataCommandLine", "(?i)sc(.exe)?\\\\s+(stop|delete|config)\\\\s+sysmon")) || + (regexMatch("log.eventDataCommandLine", "(?i)net(.exe)?\\\\s+stop\\\\s+sysmon") || + regexMatch("log.eventDataCommandLine", "(?i)net(.exe)?\\\\s+stop\\\\s+sysmon")) || + (regexMatch("log.eventDataCommandLine", "(?i)fltMC(.exe)?\\\\s+unload\\\\s+sysmon") || + regexMatch("log.eventDataCommandLine", "(?i)fltMC(.exe)?\\\\s+unload\\\\s+sysmon")) || + (regexMatch("log.eventDataCommandLine", "(?i)taskkill.*/f.*/im\\\\s+sysmon") || + regexMatch("log.eventDataCommandLine", "(?i)taskkill.*/f.*/im\\\\s+sysmon")) + ) +) || +( + equals("log.eventCode", "7045") && + regexMatch("log.eventDataServiceName", "(?i)sysmon") && + !equals("log.eventDataServiceType", "kernel mode driver") +) +', '2026-02-23 16:18:50.172615', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1038, 'Suspicious Service Installation Detection', 3, 3, 2, 'Persistence', 'T1543.003 - Create or Modify System Process: Windows Service', e'Detects installation of suspicious Windows services matching patterns from Cobalt Strike, Metasploit, +Impacket PSExec, and Meterpreter payloads. Attackers commonly install malicious services for persistence, +privilege escalation (getsystem), and lateral movement. The rule monitors Event ID 4697 for service +installations with characteristic names, binary paths, or command-line patterns used by offensive frameworks. + +Next Steps: +1. Examine the service binary path for malicious executables or scripts +2. Check if the service name matches known offensive tool patterns +3. Verify the installing user account and their authorization level +4. Review the service configuration for suspicious startup types +5. Stop and disable the suspicious service immediately +6. Analyze the service binary in a sandbox environment +7. Search for lateral movement indicators from the source host +8. Check for other persistence mechanisms installed by the same actor +', '["https://attack.mitre.org/techniques/T1543/003/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4697","https://www.sans.org/blog/red-team-tactics-hiding-windows-services/"]', e'equals("log.eventCode", "4697") && +equals("log.channel", "Security") && +( + regexMatch("log.eventDataServiceFileName", "(?i)(cmd\\\\.exe\\\\s*/c|powershell|pwsh|%COMSPEC%)") || + regexMatch("log.eventDataServiceFileName", "(?i)(\\\\\\\\ADMIN\\\\$|\\\\\\\\C\\\\$|127\\\\.0\\\\.0\\\\.1)") || + regexMatch("log.eventDataServiceFileName", "(?i)(rundll32|msiexec|regsvr32|mshta|certutil|bitsadmin)") || + regexMatch("log.eventDataServiceName", "(?i)^[a-zA-Z]{4,8}$") || + regexMatch("log.eventDataServiceFileName", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") || + regexMatch("log.eventDataServiceFileName", "(?i)\\\\.exe\\\\s+[a-zA-Z0-9+/=]{50,}") || + regexMatch("log.eventDataServiceName", "(?i)(BTOBTO|meterpreter|msf|payload|beacon|cobalt)") || + regexMatch("log.eventDataServiceFileName", "(?i)(comsvcs\\\\.dll|MiniDump|procdump|lsass)") +) +', '2026-02-23 16:18:51.276047', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1039, 'Startup Folder Persistence Detection', 2, 3, 2, 'Persistence', 'T1547.001 - Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder', e'Detects file creation or modification in Windows Startup folders, which execute programs +automatically on user login. Attackers drop malicious scripts, executables, or shortcut files +into Startup folders for persistence. The rule monitors both per-user and system-wide Startup +directories for suspicious file types. + +Next Steps: +1. Identify the file dropped into the Startup folder and analyze its contents +2. Check the parent process that created the file for suspicious origins +3. Verify if the file is a legitimate application shortcut or a malicious payload +4. Remove the suspicious file from the Startup folder +5. Analyze the executable or script for malicious behavior in a sandbox +6. Search for additional persistence mechanisms on the same host +7. Investigate the initial access vector +', '["https://attack.mitre.org/techniques/T1547/001/","https://docs.microsoft.com/en-us/windows/win32/shell/manage-program-startup","https://www.cybereason.com/blog/persistence-techniques-that-persist"]', e'( + equals("log.eventCode", "11") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + ( + regexMatch("log.eventDataTargetFilename", "(?i)\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\Startup\\\\\\\\") || + regexMatch("log.eventDataTargetFilename", "(?i)\\\\\\\\ProgramData\\\\\\\\Microsoft\\\\\\\\Windows\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\StartUp\\\\\\\\") + ) && + regexMatch("log.eventDataTargetFilename", "(?i)\\\\.(exe|dll|bat|cmd|vbs|vbe|js|jse|wsf|wsh|ps1|hta|lnk|scr|pif)$") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + (regexMatch("log.eventDataCommandLine", "(?i)copy.*\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\Startup") || + regexMatch("log.eventDataCommandLine", "(?i)copy.*\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\Startup")) || + (regexMatch("log.eventDataCommandLine", "(?i)move.*\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\Startup") || + regexMatch("log.eventDataCommandLine", "(?i)move.*\\\\\\\\Start Menu\\\\\\\\Programs\\\\\\\\Startup")) + ) +) +', '2026-02-23 16:18:52.325125', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1040, 'SMBv1 Usage Detection', 3, 2, 2, 'Lateral Movement', 'T1210 - Exploitation of Remote Services', e'Detects usage of the deprecated and vulnerable SMBv1 protocol which could be exploited for lateral movement or ransomware propagation. SMBv1 is susceptible to numerous security vulnerabilities including EternalBlue and should be disabled in favor of SMBv2/SMBv3. + +Next Steps: +1. Immediately investigate the source system using SMBv1 and identify which service or application is still dependent on this protocol +2. Review network traffic logs to determine if this is internal communication or external access attempts +3. Check for any signs of exploitation attempts or successful compromises on the affected system +4. Identify all systems in the environment that may still have SMBv1 enabled +5. Plan migration to SMBv2/SMBv3 and disable SMBv1 on all systems where possible +6. Monitor for any lateral movement patterns that may indicate ongoing compromise +7. Consider implementing network segmentation to limit exposure if SMBv1 cannot be immediately disabled +', '["https://learn.microsoft.com/en-us/windows-server/storage/file-server/troubleshoot/detect-enable-and-disable-smbv1-v2-v3","https://attack.mitre.org/techniques/T1210/"]', 'equals("log.eventId", "3000") && equals("log.providerName", "Microsoft-Windows-SMBServer") && contains("log.message", "SMB1")', '2026-02-23 16:18:53.458215', true, true, 'origin', null, '[]', '["adversary.host","adversary.ip"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1041, 'Sliver C2 Framework Detection', 3, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', e'Detects Sliver C2 framework execution patterns and artifacts. Sliver is an increasingly popular +open-source C2 framework replacing Cobalt Strike in many threat actor toolkits. The rule monitors +for characteristic Sliver implant patterns including specific PowerShell stager patterns, +named pipe names, and executable naming conventions. + +Next Steps: +1. Identify the Sliver implant type (HTTP, HTTPS, mTLS, DNS, WireGuard) +2. Check for network beaconing to C2 infrastructure +3. Review the process tree for injection and post-exploitation activity +4. Memory scan affected processes for Sliver implant shellcode +5. Isolate the compromised host immediately +6. Block identified C2 domains and IPs at the network perimeter +7. Hunt for Sliver artifacts across all endpoints +8. Review initial access vector (phishing, exploitation, etc.) +', '["https://github.com/BishopFox/sliver","https://www.microsoft.com/en-us/security/blog/2022/08/24/looking-for-the-sliver-lining-hunting-for-emerging-command-and-control-frameworks/","https://www.cybereason.com/blog/sliver-c2-leveraged-by-many-threat-actors"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataCommandLine", "(?i)(sliver|sliverpb|silver-implant)") || + regexMatch("log.eventDataCommandLine", "(?i)(sliver|sliverpb|silver-implant)") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\\\\\\\\\.\\\\\\\\pipe\\\\\\\\(sliverpb|sliver-)") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\\\\\\\\\.\\\\\\\\pipe\\\\\\\\(sliverpb|sliver-)") || + regexMatch("log.eventDataNewProcessName", "(?i)(DECISIVE_|MANAGING_|IMPRESSED_|LITERARY_|ENORMOUS_)") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\$s=New-Object\\\\s+IO\\\\.MemoryStream.*FromBase64String.*IO\\\\.Compression\\\\.GzipStream.*IO\\\\.Compression\\\\.CompressionMode.*ReadToEnd") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\$s=New-Object\\\\s+IO\\\\.MemoryStream.*FromBase64String.*IO\\\\.Compression\\\\.GzipStream") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\$s=New-Object\\\\s+IO\\\\.MemoryStream.*IO\\\\.Compression\\\\.GzipStream.*IO\\\\.Compression\\\\.CompressionMode.*ReadToEnd") || + contains("log.eventDataScriptBlockText", "sliverpb") || + regexMatch("log.eventDataScriptBlockText", "(?i)New-Object\\\\s+System\\\\.Net\\\\.Sockets\\\\.TCPClient.*IO\\\\.StreamReader.*IO\\\\.StreamWriter.*GetStream") + ) +) +', '2026-02-23 16:18:54.463056', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1042, 'Silver Ticket Attack Detection', 3, 3, 2, 'Credential Access', 'T1558.002 - Steal or Forge Kerberos Tickets: Silver Ticket', e'Detects Silver Ticket attacks where adversaries forge Kerberos TGS tickets using a service +account\'s NTLM hash, bypassing the KDC entirely. Unlike Golden Tickets, Silver Tickets target +specific services. The rule detects TGS tickets presented without corresponding TGT requests, +and service access events with anomalous Kerberos authentication patterns. + +Next Steps: +1. Identify the targeted service and its associated service account +2. Verify if the service account hash has been compromised via Kerberoasting +3. Reset the targeted service account password immediately +4. Review access logs for the targeted service for unauthorized activity +5. Check for prior Kerberoasting activity targeting the same service SPN +6. Investigate the source host for compromise indicators +7. Implement AES-only encryption for service accounts +8. Enable Kerberos PAC validation on the targeted services +', '["https://attack.mitre.org/techniques/T1558/002/","https://adsecurity.org/?p=2011","https://www.sans.org/blog/kerberos-in-the-crosshairs-golden-tickets-silver-tickets-mitm-and-more/"]', e'equals("log.eventCode", "4769") && +equals("log.channel", "Security") && +( + equals("log.eventDataTicketEncryptionType", "0x17") && + !regexMatch("log.eventDataServiceName", "(?i)(krbtgt|\\\\$$)") && + !oneOf("log.eventDataStatus", ["0x0", "0x6"]) && + exists("log.eventDataIpAddress") +) +', '2026-02-23 16:18:55.551668', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{log.eventDataIpAddress}}"}],"or":null,"within":"now-15m","count":5}]', '["lastEvent.log.eventDataIpAddress","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1043, 'SID History Injection Attempt', 3, 3, 1, 'Defense Evasion, Privilege Escalation', 'T1134.005 - Access Token Manipulation: SID-History Injection', e'Detects attempts to add SID History to an account, which can be used for privilege escalation. SID History injection allows attackers to inherit permissions from privileged accounts without being members of privileged groups. Both successful (4765) and failed (4766) attempts are monitored. + +Next Steps: +1. Immediately investigate the target user account and verify if SID History modification was legitimate +2. Check if the user performing the action has proper administrative privileges for this operation +3. Review the source SID being added to understand what permissions are being inherited +4. Examine recent authentication logs for the target account to identify potential unauthorized access +5. Verify Active Directory configuration and check for signs of domain controller compromise +6. Consider resetting the target account password and removing unauthorized SID History entries +7. Review domain administrator accounts and privileged group memberships for anomalies +', '["https://attack.mitre.org/techniques/T1134/005/","https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4765","https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4766","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=4765"]', e'oneOf("log.eventId", ["4765", "4766"]) && +equals("log.channel", "Security") +', '2026-02-23 16:18:56.694349', true, true, 'origin', null, '[]', '["adversary.user","target.host","target.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1044, 'Shadow Credentials Attack Detection', 3, 3, 1, 'Credential Access', 'T1556 - Modify Authentication Process', e'Detects Shadow Credentials attacks where adversaries modify the msDS-KeyCredentialLink attribute +on Active Directory objects to add their own key credentials for passwordless authentication. +This enables attackers to authenticate as the target account without knowing the password, +effectively taking over the account. The rule monitors Event ID 5136 (directory service object +modification) and Event ID 4662 (object access) for msDS-KeyCredentialLink changes. + +Next Steps: +1. Identify the account whose msDS-KeyCredentialLink was modified +2. Check if the modification was done by an authorized admin or service +3. Examine the added key credential for malicious certificates +4. Remove unauthorized key credentials from the affected account +5. Reset the password for the affected account +6. Review who has write access to the msDS-KeyCredentialLink attribute +7. Audit all accounts for unauthorized key credential additions +8. Investigate for related tools like Whisker or pyWhisker +', '["https://attack.mitre.org/techniques/T1556/","https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab","https://github.com/eladshamir/Whisker"]', e'( + equals("log.eventCode", "5136") && + equals("log.channel", "Security") && + contains("log.eventDataAttributeLDAPDisplayName", "msDS-KeyCredentialLink") +) || +( + equals("log.eventCode", "4662") && + equals("log.channel", "Security") && + contains("log.eventDataProperties", "msDS-KeyCredentialLink") && + !regexMatch("log.eventDataSubjectUserName", "(?i)\\\\$$") +) +', '2026-02-23 16:18:57.869062', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1045, 'Suspicious Scheduled Task Persistence', 2, 3, 2, 'Persistence', 'T1053.005 - Scheduled Task/Job: Scheduled Task', e'Detects creation of scheduled tasks from suspicious locations or executing suspicious binaries, +which is one of the most common persistence mechanisms used by attackers. The rule monitors +Event ID 4698 (scheduled task creation) for tasks that reference suspicious paths such as +Temp, Downloads, AppData, or ProgramData directories, or execute known LOLBINs like +PowerShell, certutil, rundll32, mshta, regsvr32, or cmd.exe with suspicious arguments. + +Next Steps: +1. Examine the full TaskContent XML to understand what the scheduled task executes +2. Identify the user account that created the task and verify authorization +3. Check the task execution path for malicious payloads or scripts +4. Review the trigger schedule for persistence timing patterns +5. Remove the suspicious scheduled task immediately +6. Search for additional persistence mechanisms on the same host +7. Investigate the initial access vector that led to task creation +', '["https://attack.mitre.org/techniques/T1053/005/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4698","https://posts.specterops.io/abstracting-scheduled-task-creation-6d22febc27e7"]', e'equals("log.eventCode", "4698") && +equals("log.channel", "Security") && +( + regexMatch("log.eventDataTaskContent", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Public\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") || + regexMatch("log.eventDataTaskContent", "(?i)(powershell|pwsh|cmd\\\\.exe|certutil|mshta|rundll32|regsvr32|cscript|wscript|msiexec|bitsadmin)") || + regexMatch("log.eventDataTaskContent", "(?i)(-enc\\\\s|-encodedcommand|-nop|-w\\\\s+hidden|downloadstring|invoke-expression|iex)") || + regexMatch("log.eventDataTaskContent", "(?i)(http://|https://|ftp://|\\\\\\\\\\\\\\\\[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\\\\\)") +) && +!regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE)$") +', '2026-02-23 16:18:59.041587', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1046, 'SAM Database Access Attempt', 3, 3, 1, 'Credential Access', 'T1003.002 - OS Credential Dumping: Security Account Manager', e'Detects attempts to access the Security Account Manager (SAM) database, which contains local user account hashes. This activity may indicate credential dumping attempts by attackers trying to extract password hashes for offline cracking or lateral movement. + +Next Steps: +1. Immediately investigate the user account and process that accessed the SAM database +2. Check for any unusual processes running on the affected system +3. Review recent logon events and privilege escalation activities +4. Examine network connections from the affected host for lateral movement +5. Consider isolating the affected system if malicious activity is confirmed +6. Review security policies for SAM database access permissions +7. Check for presence of credential dumping tools or suspicious files +', '["https://attack.mitre.org/techniques/T1003/002/","https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4661"]', e'equals("log.eventCode", "4663") && +equals("log.channel", "Security") && +( + endsWith("log.eventDataObjectName", "\\\\SAM") || + endsWith("log.eventDataObjectName", "\\\\SECURITY") || + endsWith("log.eventDataObjectName", "\\\\SYSTEM") +) && +oneOf("log.eventDataAccessMask", ["0x20019", "0x1f01ff", "0x40", "0x20", "0x1"]) +', '2026-02-23 16:19:00.316495', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1047, 'Rundll32 Suspicious Abuse Detection', 2, 3, 2, 'Defense Evasion', 'T1218.011 - System Binary Proxy Execution: Rundll32', e'Detects suspicious rundll32.exe usage patterns including loading DLLs from non-system paths, +comsvcs.dll MiniDump for LSASS dumping, JavaScript/VBScript execution, and loading from Temp +or Downloads directories. Rundll32 is a signed Microsoft binary frequently abused for defense +evasion and code execution. + +Next Steps: +1. Examine the DLL path and export function being called +2. Verify the DLL is not loading from a suspicious location (Temp, Downloads, AppData) +3. Check the parent process for delivery mechanism indicators +4. Analyze the loaded DLL in a sandbox environment +5. Review network connections made by the rundll32 process +6. Check for process injection from the rundll32 process +7. Search for the same DLL hash across other endpoints +', '["https://attack.mitre.org/techniques/T1218/011/","https://lolbas-project.github.io/lolbas/Binaries/Rundll32/","https://redcanary.com/threat-detection-report/techniques/rundll32/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)rundll32\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)rundll32\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)comsvcs\\\\.dll.*(MiniDump|#24)") || + regexMatch("log.eventDataCommandLine", "(?i)(javascript:|vbscript:)") || + regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\).*\\\\.dll") || + regexMatch("log.eventDataCommandLine", "(?i)shell32\\\\.dll.*ShellExec_RunDLL") || + regexMatch("log.eventDataCommandLine", "(?i)advpack\\\\.dll.*RegisterOCX") || + regexMatch("log.eventDataCommandLine", "(?i)url\\\\.dll.*FileProtocolHandler") || + regexMatch("log.eventDataCommandLine", "(?i)zipfldr\\\\.dll.*RouteTheCall") || + regexMatch("log.eventDataCommandLine", "(?i)comsvcs\\\\.dll.*(MiniDump|#24)") || + regexMatch("log.eventDataCommandLine", "(?i)(javascript:|vbscript:)") || + regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\).*\\\\.dll") +) +', '2026-02-23 16:19:01.465752', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1048, 'Registry Run Key Persistence Detection', 2, 3, 2, 'Persistence', 'T1547.001 - Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder', e'Detects modifications to Windows Registry Run and RunOnce keys, which are the most fundamental +Windows persistence mechanism. Attackers add entries to these keys to execute malicious code +every time a user logs on or the system starts. The rule monitors Event ID 4657 (registry value +modifications) for changes to Run/RunOnce keys with suspicious values pointing to unusual +executable paths, scripts, or LOLBINs. + +Next Steps: +1. Examine the registry value data to identify the persistence payload +2. Verify if the modification was made by a legitimate software installer +3. Check the modifying process and user account for compromise indicators +4. Remove the malicious registry entry immediately +5. Analyze the payload binary or script referenced in the registry value +6. Search for additional persistence mechanisms on the same host +7. Review the timeline of events leading to the registry modification +', '["https://attack.mitre.org/techniques/T1547/001/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4657","https://docs.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys"]', e'equals("log.eventCode", "4657") && +equals("log.channel", "Security") && +regexMatch("log.eventDataObjectName", "(?i)(\\\\\\\\Run\\\\\\\\|\\\\\\\\RunOnce\\\\\\\\|\\\\\\\\RunOnceEx\\\\\\\\|\\\\\\\\RunServices\\\\\\\\|\\\\\\\\RunServicesOnce\\\\\\\\)") && +( + regexMatch("log.eventDataObjectValueName", "(?i)(powershell|cmd|mshta|rundll32|regsvr32|certutil|wscript|cscript|bitsadmin)") || + regexMatch("log.eventDataNewValue", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") || + regexMatch("log.eventDataNewValue", "(?i)(powershell|cmd\\\\.exe\\\\s*/c|mshta|rundll32|regsvr32|certutil|wscript|cscript)") || + regexMatch("log.eventDataNewValue", "(?i)(http://|https://|\\\\\\\\\\\\\\\\[0-9]+\\\\.[0-9]+)") || + regexMatch("log.eventDataNewValue", "(?i)(-enc\\\\s|-encodedcommand|-w\\\\s+hidden)") +) +', '2026-02-23 16:19:02.639948', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1049, 'RDP Session Hijacking Detection', 3, 3, 2, 'Lateral Movement', 'T1563.002 - Remote Service Session Hijacking: RDP Hijacking', e'Detects RDP session hijacking using tscon.exe, which allows a user with SYSTEM privileges +to connect to another user\'s RDP session without authentication. This is commonly used +after privilege escalation to gain access to active sessions of other users, potentially +including domain administrators. + +Next Steps: +1. Identify which user\'s session was hijacked via tscon +2. Verify the SYSTEM-level access method used (service, scheduled task, etc.) +3. Check what actions were performed in the hijacked session +4. Review if the target session had access to sensitive resources +5. Force logout the compromised session +6. Investigate how the attacker obtained SYSTEM privileges +7. Review all active RDP sessions on the affected server +', '["https://attack.mitre.org/techniques/T1563/002/","https://medium.com/@youraveragetechnoob/rdp-session-hijacking-55ef3f85feaa","https://www.keysight.com/blogs/tech/nwvs/2022/09/21/rdp-session-hijacking"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + (regexMatch("log.eventDataCommandLine", "(?i)tscon(.exe)?\\\\s+\\\\d+\\\\s+/dest:") || + regexMatch("log.eventDataCommandLine", "(?i)tscon(.exe)?\\\\s+\\\\d+\\\\s+/dest:")) || + (regexMatch("log.eventDataCommandLine", "(?i)query\\\\s+session.*tscon") || + regexMatch("log.eventDataCommandLine", "(?i)query\\\\s+session.*tscon")) || + (regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\tscon\\\\.exe$") && + regexMatch("log.eventDataCommandLine", "(?i)\\\\d+\\\\s+/dest:")) +) +', '2026-02-23 16:19:03.780715', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1053, 'Print Spooler Exploitation Detection', 3, 3, 2, 'Privilege Escalation', 'T1068 - Exploitation for Privilege Escalation', e'Detects potential PrintNightmare exploitation attempts through suspicious print spooler activity including DLL loading and driver installation. This rule identifies suspicious activity related to the Print Spooler service that could indicate CVE-2021-34527 (PrintNightmare) exploitation attempts. + +Next Steps: +1. Investigate the affected host for unauthorized driver installations +2. Check for recent DLL files placed in the print spooler drivers directory +3. Review print spooler service logs for unusual activity +4. Examine process execution context and parent processes +5. Verify if print driver installations were authorized +6. Check for lateral movement from the affected system +7. Consider isolating the affected host if exploitation is confirmed +', '["https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-34527","https://attack.mitre.org/techniques/T1068/"]', '(equals("log.providerName", "Microsoft-Windows-PrintService") && equals("log.eventId", "316") && contains("log.message", "kernelbase.dll")) || (equals("log.eventDataProcessName", "spoolsv.exe") && contains("log.eventDataTargetFilename", "\\spool\\drivers\\x64\\") && endsWith("log.eventDataTargetFilename", ".dll"))', '2026-02-23 16:19:08.354150', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataTargetFilename","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1054, 'PowerShell Empire Detection', 3, 3, 2, 'Execution', 'T1059.001 - Command and Scripting Interpreter: PowerShell', e'Detects potential PowerShell Empire framework usage based on characteristic command patterns, obfuscation techniques, and encoded payloads commonly used by this post-exploitation framework. PowerShell Empire is a post-exploitation framework that uses PowerShell and Python agents to maintain persistence and execute commands on compromised systems. + +Next Steps: +1. Immediately isolate the affected host to prevent lateral movement +2. Analyze the complete PowerShell script block content for additional IOCs +3. Check for persistence mechanisms (scheduled tasks, registry entries, services) +4. Review network connections from the host for C2 communication +5. Examine process tree and parent processes that spawned PowerShell +6. Search for additional Empire artifacts across the environment +7. Reset credentials for any accounts used on the compromised system +8. Conduct memory analysis to identify injected code or payloads +9. Review recent user activity and file access patterns +10. Update endpoint detection rules based on specific Empire techniques observed +', '["https://attack.mitre.org/techniques/T1059/001/","https://www.powershellempire.com/","https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging"]', e'(equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && +equals("log.providerName", "Microsoft-Windows-PowerShell") && +( + contains("log.eventDataScriptBlockText", "System.Management.Automation.AmsiUtils") || + regexMatch("log.eventDataScriptBlockText", "(?i)(empire|invoke-empire|invoke-psempire)") || + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\[System\\\\.Convert\\\\]::FromBase64String") || + regexMatch("log.eventDataScriptBlockText", "(?i)IEX\\\\s*\\\\(\\\\s*New-Object") || + regexMatch("log.eventDataScriptBlockText", "(?i)-enc\\\\s+[A-Za-z0-9+/=]{100,}") || + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\$DoIt\\\\s*=\\\\s*@") || + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\[System\\\\.Text\\\\.Encoding\\\\]::Unicode\\\\.GetString") || + contains("log.eventDataScriptBlockText", "Invoke-Shellcode") || + contains("log.eventDataScriptBlockText", "Invoke-ReflectivePEInjection") || + contains("log.eventDataScriptBlockText", "Invoke-Mimikatz") +) +', '2026-02-23 16:19:09.561590', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1055, 'PetitPotam NTLM Relay Attack Detection', 3, 3, 2, 'Credential Access', 'T1187 - Forced Authentication', e'Detects PetitPotam NTLM relay attacks that abuse EFS RPC calls to coerce domain controller +authentication. The attack forces a DC to authenticate to an attacker-controlled host, enabling +NTLM relay to Active Directory Certificate Services (AD CS) for domain compromise. The rule +monitors Event ID 5145 (network share access) for IPC$ share access to specific EFS-related +named pipes used by PetitPotam. + +Next Steps: +1. Identify the source IP attempting the EFS pipe access +2. Check if the source is an authorized system or a potential attacker +3. Verify if AD CS is configured and potentially vulnerable to NTLM relay +4. Apply Microsoft patches for PetitPotam (KB5005413) +5. Enable Extended Protection for Authentication on AD CS +6. Disable NTLM authentication where possible +7. Monitor for certificate enrollment from the relayed authentication +8. Restrict access to EFS RPC endpoints via Windows Firewall rules +', '["https://attack.mitre.org/techniques/T1187/","https://github.com/topotam/PetitPotam","https://msrc.microsoft.com/update-guide/vulnerability/ADV210003"]', e'equals("log.eventCode", "5145") && +equals("log.channel", "Security") && +equals("log.eventDataShareName", "\\\\\\\\*\\\\IPC$") && +( + contains("log.eventDataRelativeTargetName", "efsrpc") || + contains("log.eventDataRelativeTargetName", "lsarpc") || + contains("log.eventDataRelativeTargetName", "efsr") || + contains("log.eventDataRelativeTargetName", "samr") +) +', '2026-02-23 16:19:10.693974', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{log.eventDataIpAddress}}"}],"or":null,"within":"now-5m","count":3}]', '["lastEvent.log.eventDataIpAddress","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1056, 'Pass-the-Ticket Attack Detection', 3, 3, 2, 'Lateral Movement', 'T1550.003 - Use Alternate Authentication Material: Pass the Ticket', e'Detects Pass-the-Ticket attacks where attackers steal and reuse Kerberos tickets from one +endpoint to authenticate on another. The rule monitors for Kerberos authentication events +(Event ID 4624 LogonType 3 with Kerberos package) combined with anomalous ticket usage +patterns and tools like Rubeus, Mimikatz kerberos::ptt, and Invoke-Mimikatz. + +Next Steps: +1. Identify the source of the Kerberos ticket and the target system +2. Check if the ticket was exported using Mimikatz or Rubeus +3. Verify if the original ticket owner\'s host is compromised +4. Review Kerberos TGS/TGT events from the source IP +5. Force Kerberos ticket renewal by resetting the user\'s password +6. Check for additional lateral movement from the authenticated session +7. Implement Kerberos constrained delegation and armoring +', '["https://attack.mitre.org/techniques/T1550/003/","https://adsecurity.org/?p=2011","https://www.sans.org/blog/kerberos-in-the-crosshairs-golden-tickets-silver-tickets-mitm-and-more/"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + (regexMatch("log.eventDataCommandLine", "(?i)kerberos::ptt") || + regexMatch("log.eventDataCommandLine", "(?i)kerberos::ptt")) || + (regexMatch("log.eventDataCommandLine", "(?i)Rubeus.*ptt") || + regexMatch("log.eventDataCommandLine", "(?i)Rubeus.*ptt")) || + (regexMatch("log.eventDataCommandLine", "(?i)Rubeus.*(asktgt|asktgs|renew|s4u|createnetonly)") || + regexMatch("log.eventDataCommandLine", "(?i)Rubeus.*(asktgt|asktgs|renew|s4u|createnetonly)")) || + (regexMatch("log.eventDataCommandLine", "(?i)Invoke-Mimikatz.*kerberos") || + regexMatch("log.eventDataCommandLine", "(?i)Invoke-Mimikatz.*kerberos")) || + (regexMatch("log.eventDataCommandLine", "(?i)klist.*purge|klist.*tickets") || + regexMatch("log.eventDataCommandLine", "(?i)klist.*purge|klist.*tickets")) + ) +) || +( + equals("log.eventCode", "4768") && + equals("log.channel", "Security") && + !equals("log.eventDataStatus", "0x0") && + oneOf("log.eventDataStatus", ["0x1f", "0x20", "0x25"]) && + exists("log.eventDataIpAddress") +) +', '2026-02-23 16:19:11.792793', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1057, 'Pass-the-Hash Attack Detection', 3, 3, 2, 'Lateral Movement', 'T1550.002 - Use Alternate Authentication Material: Pass the Hash', e'Detects Pass-the-Hash attacks by monitoring for NTLM authentication (Event ID 4624) with +LogonType 9 (NewCredentials) or LogonType 3 (Network) from unusual sources, combined with +the use of Seclogon service. Attackers use stolen NTLM hashes to authenticate without +knowing the plaintext password, commonly through tools like Mimikatz sekurlsa::pth, +Impacket, or CrackMapExec. + +Next Steps: +1. Identify the source IP and user account used for the NTLM authentication +2. Verify if the source host should be authenticating with NTLM to this target +3. Check for prior credential dumping activity on the source host +4. Review if the authentication was followed by lateral movement or data access +5. Reset the compromised account password and any related accounts +6. Implement NTLM restrictions via Group Policy where possible +7. Enable Windows Defender Credential Guard to protect NTLM hashes +', '["https://attack.mitre.org/techniques/T1550/002/","https://www.sans.org/blog/pass-the-hash-attack-detection/","https://stealthbits.com/blog/how-to-detect-pass-the-hash-attacks/"]', e'( + equals("log.eventCode", "4624") && + equals("log.channel", "Security") && + equals("log.eventDataLogonType", "9") && + equals("log.eventDataAuthenticationPackageName", "Negotiate") && + !regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE|ANONYMOUS LOGON|-|\\\\$)") && + exists("target.user") && + !regexMatch("target.user", "(?i)\\\\$$") +) || +( + equals("log.eventCode", "4624") && + equals("log.channel", "Security") && + equals("log.eventDataLogonType", "3") && + equals("log.eventDataLmPackageName", "NTLM V1") && + exists("log.eventDataIpAddress") && + !equals("log.eventDataIpAddress", "-") && + !equals("log.eventDataIpAddress", "::1") && + !equals("log.eventDataIpAddress", "127.0.0.1") +) +', '2026-02-23 16:19:12.990314', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{log.eventDataIpAddress}}"},{"field":"log.eventCode","operator":"filter_term","value":"4624"}],"or":null,"within":"now-30m","count":3}]', '["lastEvent.log.eventDataIpAddress","adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1058, 'NTLM Authentication Downgrade Attack', 3, 3, 1, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects NTLM authentication downgrade attacks via registry modifications to LMCompatibilityLevel, +NtlmMinClientSec, and NtlmMinServerSec. Attackers modify these registry values to weaken NTLM +authentication security, enabling credential interception, relay attacks, and offline cracking +of captured NTLM hashes. Downgrading to LM or NTLMv1 authentication makes credential theft +significantly easier. + +Next Steps: +1. Check the new registry value to determine the downgrade severity +2. LMCompatibilityLevel < 3 enables NTLMv1 which is trivially crackable +3. Identify the process and user that made the registry modification +4. Restore the registry values to enforce NTLMv2 (LMCompatibilityLevel = 5) +5. Review Group Policy for conflicting NTLM settings +6. Check for NTLM relay attacks following the downgrade +7. Audit network traffic for NTLMv1 authentication attempts +8. Consider disabling NTLM entirely where possible +', '["https://attack.mitre.org/techniques/T1562/001/","https://www.praetorian.com/blog/ntlm-relaying-attacks/","https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/network-security-lan-manager-authentication-level"]', e'equals("log.eventCode", "4657") && +equals("log.channel", "Security") && +( + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Lsa\\\\\\\\LMCompatibilityLevel") || + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Lsa\\\\\\\\MSV1_0\\\\\\\\NtlmMinClientSec") || + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Lsa\\\\\\\\MSV1_0\\\\\\\\NtlmMinServerSec") || + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Lsa\\\\\\\\MSV1_0\\\\\\\\RestrictSendingNTLMTraffic") || + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Control\\\\\\\\Lsa\\\\\\\\MSV1_0\\\\\\\\AuditReceivingNTLMTraffic") || + regexMatch("log.eventDataObjectName", "(?i)\\\\\\\\CurrentControlSet\\\\\\\\Services\\\\\\\\Netlogon\\\\\\\\Parameters\\\\\\\\RequireSignOrSeal") +) && +!regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|TrustedInstaller)$") +', '2026-02-23 16:19:14.046987', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1059, 'NTDS.dit Extraction Attempt', 3, 3, 1, 'Credential Access', 'T1003.003 - OS Credential Dumping: NTDS', e'Detects attempts to access or copy the Active Directory domain database (NTDS.dit) which contains password hashes for all domain users. This is a critical indicator of credential theft attempts and potential domain compromise. + +Next Steps: +1. Immediately isolate the affected system to prevent further compromise +2. Review all recent activity from the source host and user account +3. Check for signs of lateral movement from this system +4. Verify integrity of domain controllers and examine recent administrative actions +5. Look for evidence of credential harvesting tools (ntdsutil, vssadmin, mimikatz) +6. Review privileged account usage and consider forcing password resets +7. Examine network traffic for data exfiltration attempts +8. Check backup systems and shadow copies for unauthorized access +9. Coordinate with incident response team for full forensic analysis +', '["https://attack.mitre.org/techniques/T1003/003/","https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4663"]', e'oneOf("log.eventCode", ["4663", "4656"]) && +equals("log.channel", "Security") && +( + endsWith("log.eventDataObjectName", "\\\\ntds.dit") || + contains("log.eventDataObjectName", "\\\\NTDS\\\\") || + endsWith("log.eventDataProcessName", "\\\\ntdsutil.exe") || + endsWith("log.eventDataProcessName", "\\\\vssadmin.exe") +) && +!equals("log.eventDataAccessMask", "0x0") +', '2026-02-23 16:19:15.221310', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{origin.host}}"}],"or":null,"within":"now-30m","count":2}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1060, 'Network Sniffer and Packet Capture Detection', 3, 1, 0, 'Discovery', 'T1040 - Network Sniffing', e'Detects execution of network sniffing and packet capture tools that could be used to +intercept network traffic, capture credentials, or perform man-in-the-middle attacks. +Monitors for known sniffer binaries, raw socket creation, and promiscuous mode indicators. + +Next Steps: +1. Verify if the packet capture tool usage was authorized (IT/security staff) +2. Check which user account executed the tool and validate their role +3. Determine what network interfaces were targeted for capture +4. Review if any sensitive data (credentials, tokens) may have been captured +5. Check for data exfiltration - captured packets being sent to external destinations +6. Look for lateral movement from the host running the sniffer +7. Investigate if the sniffer was installed recently or is part of standard tooling +', '["https://attack.mitre.org/techniques/T1040/","https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4688","https://www.sans.org/reading-room/whitepapers/detection/detecting-network-sniffers-1180"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataNewProcessName", "(?i)(wireshark|tshark|dumpcap|windump|tcpdump|rawcap|netsh\\\\.exe|pktmon\\\\.exe)\\\\.exe$") || + regexMatch("log.eventDataNewProcessName", "(?i)(ettercap|NetworkMiner|Microsoft Network Monitor|nmcap)\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)(wireshark|tshark|dumpcap|windump|tcpdump|rawcap)\\\\.exe$") || + ( + regexMatch("log.eventDataNewProcessName", "(?i)netsh\\\\.exe$") && + regexMatch("log.eventDataCommandLine", "(?i)(trace\\\\s+start|capture\\\\s+start)") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)pktmon\\\\.exe$") && + contains("log.eventDataCommandLine", "start") + ) || + regexMatch("log.eventDataCommandLine", "(?i)(npcap|winpcap|pcap\\\\.dll|raw\\\\s+socket|promiscuous)") || + regexMatch("log.eventDataCommandLine", "(?i)(npcap|winpcap|pcap\\\\.dll|raw\\\\s+socket|promiscuous)") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + contains("log.eventDataScriptBlockText", "Net.Sockets.Socket") || + contains("log.eventDataScriptBlockText", "SocketType.Raw") || + contains("log.eventDataScriptBlockText", "ProtocolType.IP") || + contains("log.eventDataScriptBlockText", "IOControlCode") || + contains("log.eventDataScriptBlockText", "SIO_RCVALL") || + contains("log.eventDataScriptBlockText", "Invoke-PacketCapture") || + contains("log.eventDataScriptBlockText", "New-NetEventSession") + ) +) || +( + equals("log.eventCode", "7045") && + regexMatch("log.eventDataServiceName", "(?i)(npcap|winpcap|npf|rawcap)") +) +', '2026-02-23 16:19:16.331532', true, true, 'origin', '["adversary.host","adversary.user"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1061, 'Named Pipe Impersonation Attack', 3, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1134.001 - Access Token Manipulation: Token Impersonation/Theft', e'Detects potential named pipe impersonation attacks used for privilege escalation. Monitors for suspicious process creation patterns including cmd.exe or powershell.exe with pipe-related commands, and processes creating named pipes with suspicious naming patterns commonly used by attack tools like Meterpreter and Cobalt Strike. + +Next Steps: +1. Verify the legitimacy of the process and command line parameters +2. Check for any privilege escalation activities following this event +3. Examine the process parent-child relationship and execution context +4. Review recent logon events (4672) for the affected host +5. Investigate any unusual network connections or file access patterns +6. Consider isolating the affected system if malicious activity is confirmed +', '["https://bherunda.medium.com/hunting-named-pipe-token-impersonation-abuse-573dcca36ae0","https://attack.mitre.org/techniques/T1134/001/"]', e'equals("log.eventCode", "4688") && +exists("log.eventDataProcessName") && +(contains("log.eventDataProcessName", "cmd.exe") || contains("log.eventDataProcessName", "powershell.exe")) && +exists("log.eventDataProcessCommandLine") && +(contains("log.eventDataProcessCommandLine", "\\\\\\\\.\\\\pipe\\\\") || + (contains("log.eventDataProcessCommandLine", "echo") && contains("log.eventDataProcessCommandLine", "pipe")) || + contains("log.eventDataProcessCommandLine", "CreateNamedPipe") || + contains("log.eventDataProcessCommandLine", "ImpersonateNamedPipeClient")) +', '2026-02-23 16:19:17.435809', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataProcessId","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1062, 'MSIExec Remote Payload Installation', 2, 3, 2, 'Defense Evasion', 'T1218.007 - System Binary Proxy Execution: Msiexec', e'Detects abuse of msiexec.exe for installing MSI packages from remote URLs, executing DLLs, +or running packages with quiet/silent flags to avoid user interaction. Attackers use msiexec +as a proxy to execute malicious code because it is a signed Microsoft binary that can download +and execute payloads from remote locations. + +Next Steps: +1. Examine the MSI package URL or path for malicious content +2. Review the quiet/silent installation flags for evasion intent +3. Check the parent process to determine the delivery mechanism +4. Analyze the MSI package contents in a sandbox +5. Review network connections to the MSI download source +6. Block the identified URL at the proxy/firewall +7. Search for the same MSI package hash across other endpoints +', '["https://attack.mitre.org/techniques/T1218/007/","https://lolbas-project.github.io/lolbas/Binaries/Msiexec/","https://www.trendmicro.com/en_us/research/19/b/msiexec-abuse.html"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)msiexec\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)msiexec\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)/i\\\\s+(http://|https://)") || + regexMatch("log.eventDataCommandLine", "(?i)/y\\\\s+") || + regexMatch("log.eventDataCommandLine", "(?i)/z\\\\s+(http://|https://)") || + ( + regexMatch("log.eventDataCommandLine", "(?i)(/q|/quiet|/passive)") && + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|\\\\\\\\\\\\\\\\)") + ) || + regexMatch("log.eventDataCommandLine", "(?i)/i\\\\s+(http://|https://)") || + regexMatch("log.eventDataCommandLine", "(?i)/y\\\\s+") || + ( + regexMatch("log.eventDataCommandLine", "(?i)(/q|/quiet|/passive)") && + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|\\\\\\\\\\\\\\\\)") + ) +) +', '2026-02-23 16:19:18.532899', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1063, 'MSHTA Suspicious Execution Detection', 2, 3, 2, 'Defense Evasion', 'T1218.005 - System Binary Proxy Execution: Mshta', e'Detects suspicious mshta.exe execution patterns including remote HTA file execution, inline +VBScript/JavaScript execution, and COM scriptlet execution. MSHTA is a signed Microsoft binary +that executes Microsoft HTML Applications (HTA) and is frequently abused for initial access, +defense evasion, and payload delivery because it bypasses application whitelisting. + +Next Steps: +1. Examine the full command line for remote URLs or inline script content +2. Identify the parent process to determine the initial delivery mechanism +3. Check for downloaded HTA files and analyze their contents +4. Review network connections made by the mshta.exe process +5. Block identified malicious URLs at the proxy/firewall +6. Search for similar mshta executions across other endpoints +7. Check for persistence mechanisms established after mshta execution +', '["https://attack.mitre.org/techniques/T1218/005/","https://lolbas-project.github.io/lolbas/Binaries/Mshta/","https://redcanary.com/threat-detection-report/techniques/mshta/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)mshta\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)mshta\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") || + regexMatch("log.eventDataCommandLine", "(?i)(vbscript:|javascript:)") || + regexMatch("log.eventDataCommandLine", "(?i)(GetObject|Execute|CreateObject|WScript\\\\.Shell)") || + regexMatch("log.eventDataCommandLine", "(?i)sct:") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\.hta\\\\s") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") || + regexMatch("log.eventDataCommandLine", "(?i)(vbscript:|javascript:)") || + regexMatch("log.eventDataCommandLine", "(?i)(GetObject|Execute|CreateObject|WScript\\\\.Shell)") +) +', '2026-02-23 16:19:19.648137', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1064, 'Mimikatz Tool Usage Detection', 3, 3, 1, 'Credential Access', 'T1003.001 - OS Credential Dumping: LSASS Memory', e'Detects potential Mimikatz credential dumping tool usage through various indicators including characteristic command patterns, LSASS access, and known Mimikatz modules. Mimikatz is a well-known post-exploitation tool used to extract plaintext passwords, hash, PIN code and kerberos tickets from memory. + +Next Steps: +1. Immediately isolate the affected host to prevent lateral movement +2. Review the full command line and process execution details +3. Check for any credential theft or privilege escalation activities +4. Examine recent logon events and account usage patterns +5. Scan for additional persistence mechanisms or backdoors +6. Reset passwords for all potentially compromised accounts +7. Review security logs for signs of lateral movement to other systems +8. Conduct forensic analysis of memory dumps and system artifacts +', '["https://attack.mitre.org/techniques/T1003/001/","https://github.com/gentilkiwi/mimikatz","https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4688"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataNewProcessName", "(?i)mimikatz") || + regexMatch("log.eventDataCommandLine", "(?i)(sekurlsa|kerberos|crypto|vault|lsadump|dpapi)::") || + regexMatch("log.eventDataCommandLine", "(?i)(logonpasswords|pth|golden|silver|ticket)") || + regexMatch("log.eventDataCommandLine", "(?i)privilege::debug") || + contains("log.eventDataCommandLine", "coffee") || + contains("log.eventDataCommandLine", "kirbi") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + regexMatch("log.eventDataScriptBlockText", "(?i)invoke-mimikatz") || + regexMatch("log.eventDataScriptBlockText", "(?i)mimikatz\\\\.ps1") || + regexMatch("log.eventDataScriptBlockText", "(?i)DumpCreds|DumpCerts") || + contains("log.eventDataScriptBlockText", "Win32_ShadowCopy") + ) +) || +( + equals("log.eventCode", "10") && + regexMatch("log.eventDataTargetImage", "(?i)lsass\\\\.exe") && + oneOf("log.eventDataGrantedAccess", ["0x1010", "0x1038", "0x1418", "0x1438"]) +) +', '2026-02-23 16:19:20.709892', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1065, 'Process Masquerading Detection', 2, 3, 2, 'Defense Evasion', 'T1036.005 - Masquerading: Match Legitimate Name or Location', e'Detects executables masquerading as legitimate Windows system processes but running from +incorrect locations. For example, svchost.exe should only run from C:\\Windows\\System32, +and explorer.exe should only run from C:\\Windows. Malware commonly uses legitimate process +names to avoid detection by analysts and automated tools. + +Next Steps: +1. Identify the actual file path of the masquerading process +2. Compare the file hash against known good versions of the legitimate binary +3. Check the digital signature of the suspicious executable +4. Analyze the executable in a sandbox environment +5. Review the parent process that launched the masquerading binary +6. Kill the suspicious process and quarantine the file +7. Search for other instances of the same file across the environment +', '["https://attack.mitre.org/techniques/T1036/005/","https://www.elastic.co/blog/how-hunt-masquerade-ball","https://redcanary.com/threat-detection-report/techniques/masquerading/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\svchost\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\(System32|SysWOW64)\\\\\\\\svchost\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\csrss\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\(System32|SysWOW64)\\\\\\\\csrss\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\lsass\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\lsass\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\services\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\services\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\smss\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\smss\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\wininit\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\wininit\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)\\\\\\\\explorer\\\\.exe$") && + !regexMatch("log.eventDataNewProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\explorer\\\\.exe$") + ) || + ( + regexMatch("log.eventDataProcessName", "(?i)\\\\\\\\svchost\\\\.exe$") && + !regexMatch("log.eventDataProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\(System32|SysWOW64)\\\\\\\\svchost\\\\.exe$") + ) || + ( + regexMatch("log.eventDataProcessName", "(?i)\\\\\\\\lsass\\\\.exe$") && + !regexMatch("log.eventDataProcessName", "(?i)^C:\\\\\\\\Windows\\\\\\\\System32\\\\\\\\lsass\\\\.exe$") + ) +) +', '2026-02-23 16:19:21.854705', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1077, 'Event Log Clearing Detection', 3, 3, 2, 'Defense Evasion', 'T1070.001 - Indicator Removal on Host: Clear Windows Event Logs', e'Detects when Windows event logs are cleared, which is often done by attackers to cover their tracks and remove evidence of malicious activities. This rule monitors for Event ID 1102 (Security log cleared), Event ID 104 (System log cleared), and command-line activities using wevtutil or PowerShell cmdlets to clear event logs. + +Next Steps: +1. Identify the user account that performed the log clearing operation +2. Review the timeline of events before the log clearing to identify potential malicious activities +3. Check for any remaining forensic artifacts in other log sources (network logs, endpoint logs, etc.) +4. Investigate if this was authorized maintenance or potential malicious activity +5. Review user privileges and access patterns for the account involved +6. Consider implementing additional logging and monitoring for critical systems +', '["https://attack.mitre.org/techniques/T1070/001/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=1102"]', e'equals("log.eventId", "1102") || +equals("log.eventCode", "1102") || +(equals("log.eventId", "104") && + contains("log.channel", "System")) || +(equals("log.eventId", "4688") && + ((contains("log.eventDataCommandLine", "wevtutil") && + contains("log.eventDataCommandLine", " cl ")) || + contains("log.eventDataCommandLine", "Clear-EventLog") || + contains("log.eventDataCommandLine", "Remove-EventLog") || + contains("log.eventDataCommandLine", "Limit-EventLog"))) +', '2026-02-23 16:19:35.038887', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1078, 'ETW Patching and Tampering Detection', 3, 3, 2, 'Defense Evasion', 'T1562.006 - Impair Defenses: Indicator Blocking', e'Detects attempts to tamper with Event Tracing for Windows (ETW) to blind security tools. +Attackers patch ETW functions in memory (NtTraceEvent, EtwEventWrite) or use PowerShell +Reflection to disable .NET ETW providers, preventing security tools from receiving telemetry. +This technique is frequently used before executing post-exploitation tools to evade detection. + +Next Steps: +1. Investigate the process that attempted ETW tampering +2. Check what malicious activity followed the ETW patching +3. Review PowerShell script block logs for ETW manipulation code +4. Examine if the .NET ETW provider was targeted (common for AMSI bypass chains) +5. Verify that ETW providers are functioning correctly after remediation +6. Search for additional evasion techniques on the same host +7. Isolate the host and investigate the full attack chain +', '["https://attack.mitre.org/techniques/T1562/006/","https://blog.xpnsec.com/hiding-your-dotnet-etw/","https://www.mdsec.co.uk/2020/03/hiding-your-net-etw/"]', e'( + (equals("log.eventCode", "4104") || equals("log.eventCode", "4103")) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + contains("log.eventDataScriptBlockText", "EtwEventWrite") || + contains("log.eventDataScriptBlockText", "NtTraceEvent") || + contains("log.eventDataScriptBlockText", "NtTraceControl") || + regexMatch("log.eventDataScriptBlockText", "(?i)\\\\[Reflection\\\\.Assembly\\\\].*ETW") || + regexMatch("log.eventDataScriptBlockText", "(?i)Patch.*ETW|ETW.*Patch") || + contains("log.eventDataScriptBlockText", "EventProvider") || + contains("log.eventDataScriptBlockText", "EtwEventWrite") || + contains("log.eventDataScriptBlockText", "NtTraceEvent") + ) +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + regexMatch("log.eventDataCommandLine", "(?i)logman.*stop.*EventLog") || + regexMatch("log.eventDataCommandLine", "(?i)logman.*stop.*EventLog") || + regexMatch("log.eventDataCommandLine", "(?i)auditpol.*/clear") || + regexMatch("log.eventDataCommandLine", "(?i)auditpol.*/clear") || + regexMatch("log.eventDataCommandLine", "(?i)Set-EtwTraceProvider.*0x0") || + regexMatch("log.eventDataCommandLine", "(?i)Set-EtwTraceProvider.*0x0") + ) +) +', '2026-02-23 16:19:36.223267', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1079, 'DPAPI Domain Backup Key Extraction', 3, 3, 1, 'Credential Access', 'T1003.004 - OS Credential Dumping: LSA Secrets', e'Detects extraction of the DPAPI domain backup key from a domain controller. The BCKUPKEY secret +object contains the domain\'s DPAPI backup key which can be used to decrypt any DPAPI-protected +data for all domain users, including passwords, certificates, and other sensitive data. This is +a high-severity credential access technique that indicates an attacker has domain admin access. + +Next Steps: +1. Verify the account accessing the BCKUPKEY object is authorized (should only be DC replication) +2. Check if the source host is a legitimate domain controller +3. Review the user account for signs of compromise +4. Investigate for related DCSync or NTDS.dit extraction attempts +5. Assume all DPAPI-protected secrets are compromised if unauthorized +6. Rotate the DPAPI domain backup key (requires careful planning) +7. Reset credentials for all privileged accounts +', '["https://attack.mitre.org/techniques/T1003/004/","https://www.dsinternals.com/en/retrieving-dpapi-backup-keys-from-active-directory/","https://adsecurity.org/?p=1785"]', e'equals("log.eventCode", "4662") && +equals("log.channel", "Security") && +contains("log.eventDataProperties", "BCKUPKEY") && +contains("log.eventDataObjectServer", "DS") && +!regexMatch("log.eventDataSubjectUserName", "(?i)\\\\$$") +', '2026-02-23 16:19:37.411333', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1080, 'Domain Trust Discovery Detection', 2, 1, 1, 'Discovery', 'T1482 - Domain Trust Discovery', e'Detects domain trust enumeration using nltest, dsquery, Get-ADTrust, and other Active +Directory discovery tools. Attackers enumerate domain trusts to identify additional targets +for lateral movement between trusted domains and forests. This is typically an early +reconnaissance step in Active Directory attacks. + +Next Steps: +1. Identify the user account performing domain trust enumeration +2. Verify if this is authorized security assessment or IT administration +3. Check for other discovery commands from the same user or host +4. Review if the user subsequently accessed resources in trusted domains +5. Correlate with other reconnaissance activities (BloodHound, SharpHound) +6. Monitor for lateral movement into discovered trusted domains +7. Review trust configurations for unnecessary or overly permissive trusts +', '["https://attack.mitre.org/techniques/T1482/","https://adsecurity.org/?p=1588","https://www.harmj0y.net/blog/redteaming/a-guide-to-attacking-domain-trusts/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + (regexMatch("log.eventDataCommandLine", "(?i)nltest(.exe)?\\\\s+/(domain_trusts|trusted_domains|dclist|dsgetdc)") || + regexMatch("log.eventDataCommandLine", "(?i)nltest(.exe)?\\\\s+/(domain_trusts|trusted_domains|dclist|dsgetdc)")) || + (regexMatch("log.eventDataCommandLine", "(?i)dsquery(.exe)?\\\\s+trust") || + regexMatch("log.eventDataCommandLine", "(?i)dsquery(.exe)?\\\\s+trust")) || + (regexMatch("log.eventDataCommandLine", "(?i)Get-ADTrust|Get-DomainTrust|Get-ForestTrust|Get-NetForestTrust") || + regexMatch("log.eventDataCommandLine", "(?i)Get-ADTrust|Get-DomainTrust|Get-ForestTrust|Get-NetForestTrust")) || + (regexMatch("log.eventDataCommandLine", "(?i)\\\\[System\\\\.DirectoryServices\\\\.ActiveDirectory\\\\.(Domain|Forest)\\\\]") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\[System\\\\.DirectoryServices\\\\.ActiveDirectory\\\\.(Domain|Forest)\\\\]")) || + (regexMatch("log.eventDataCommandLine", "(?i)adfind(.exe)?.*-f.*trustdirection") || + regexMatch("log.eventDataCommandLine", "(?i)adfind(.exe)?.*-f.*trustdirection")) +) +', '2026-02-23 16:19:38.636625', true, true, 'origin', '["adversary.host","adversary.user"]', '[]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1068, 'LSASS Credential Dumping Detection', 3, 3, 1, 'Credential Access', 'T1003.001 - OS Credential Dumping: LSASS Memory', e'Detects multiple LSASS credential dumping techniques beyond Mimikatz, including comsvcs.dll MiniDump, +procdump, nanodump, pypykatz, Task Manager dump, rundll32 with comsvcs.dll, and direct NT API calls. +LSASS holds credentials for all authenticated users and is a primary target for credential theft. +This rule complements the existing Mimikatz detection by covering alternative dumping methods. + +Next Steps: +1. Immediately isolate the affected host to prevent lateral movement +2. Identify the dumping technique used and the resulting dump file location +3. Check if the dump file has been exfiltrated to an external destination +4. Review the process tree to understand how the dumping tool was executed +5. Reset passwords for all accounts that were logged into the compromised system +6. Enable Credential Guard or RunAsPPL to protect LSASS +7. Search for the same dumping technique across other endpoints +8. Investigate the initial compromise vector +', '["https://attack.mitre.org/techniques/T1003/001/","https://www.microsoft.com/en-us/security/blog/2022/10/05/detecting-and-preventing-lsass-credential-dumping-attacks/","https://redcanary.com/threat-detection-report/techniques/lsass-memory/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + ( + regexMatch("log.eventDataCommandLine", "(?i)comsvcs\\\\.dll[,\\\\s]+(MiniDump|#24)") || + regexMatch("log.eventDataCommandLine", "(?i)comsvcs\\\\.dll[,\\\\s]+(MiniDump|#24)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)procdump.*-ma\\\\s+(lsass|\\\\d+)") || + regexMatch("log.eventDataCommandLine", "(?i)procdump.*-ma\\\\s+(lsass|\\\\d+)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)rundll32\\\\.exe.*comsvcs") || + regexMatch("log.eventDataCommandLine", "(?i)rundll32\\\\.exe.*comsvcs") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)(nanodump|handlekatz|physmem2profit|dumpert)") || + regexMatch("log.eventDataCommandLine", "(?i)(nanodump|handlekatz|physmem2profit|dumpert)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)taskmgr.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)taskmgr.*lsass") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)(Out-Minidump|Get-Process.*lsass.*\\\\.DumpProcess)") || + regexMatch("log.eventDataCommandLine", "(?i)(Out-Minidump|Get-Process.*lsass.*\\\\.DumpProcess)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)sqldumper\\\\.exe.*lsass") || + regexMatch("log.eventDataCommandLine", "(?i)sqldumper\\\\.exe.*lsass") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)createdump\\\\.exe.*(-f|--full)") || + regexMatch("log.eventDataCommandLine", "(?i)createdump\\\\.exe.*(-f|--full)") + ) +) +', '2026-02-23 16:19:24.740211', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1069, 'Regsvr32 LOLBIN Abuse Detection', 3, 3, 2, 'Defense Evasion', 'T1218.010 - System Binary Proxy Execution: Regsvr32', e'Detects suspicious regsvr32.exe usage including Squiblydoo attacks (loading remote SCT files), +AppLocker bypass techniques, and execution of DLLs from suspicious locations. Regsvr32 is a +signed Microsoft binary that can be abused to proxy execution of malicious code, bypassing +application whitelisting controls. + +Next Steps: +1. Examine the DLL or SCT file being loaded by regsvr32 +2. Check if a remote URL was used (Squiblydoo attack indicator) +3. Analyze the parent process that launched regsvr32 +4. Review network connections made by the regsvr32 process +5. Check if the loaded DLL is from a suspicious location (Temp, AppData, etc.) +6. Block the remote URL if a network-based attack was used +7. Investigate what payload was delivered through the proxy execution +', '["https://attack.mitre.org/techniques/T1218/010/","https://pentestlab.blog/2017/05/11/applocker-bypass-regsvr32/","https://lolbas-project.github.io/lolbas/Binaries/Regsvr32/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + regexMatch("log.eventDataNewProcessName", "(?i)regsvr32\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)regsvr32\\\\.exe$") +) && +( + (regexMatch("log.eventDataCommandLine", "(?i)/s.*/u.*/i:") || + regexMatch("log.eventDataCommandLine", "(?i)/s.*/u.*/i:")) || + (regexMatch("log.eventDataCommandLine", "(?i)/i:(http|https|ftp)://") || + regexMatch("log.eventDataCommandLine", "(?i)/i:(http|https|ftp)://")) || + (regexMatch("log.eventDataCommandLine", "(?i)\\\\.sct") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\.sct")) || + (regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") || + regexMatch("log.eventDataCommandLine", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)")) || + (regexMatch("log.eventDataCommandLine", "(?i)scrobj\\\\.dll") || + regexMatch("log.eventDataCommandLine", "(?i)scrobj\\\\.dll")) +) +', '2026-02-23 16:19:25.874536', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1070, 'LaZagne Credential Harvester Detection', 3, 2, 1, 'Credential Access', 'T1555 - Credentials from Password Stores', e'Detects LaZagne credential harvesting tool execution. LaZagne is an open-source tool that +retrieves credentials stored on a local computer from 25+ sources including browsers, email +clients, databases, Wi-Fi passwords, Windows credentials, and more. The rule monitors for +both the executable name and characteristic command-line arguments used by LaZagne. + +Next Steps: +1. Immediately isolate the affected host to prevent credential abuse +2. Identify all credential stores that may have been accessed +3. Review the command-line arguments to determine which modules were used +4. Reset passwords for all accounts stored on the compromised system +5. Check browser credential stores, email clients, and Windows vaults +6. Review Wi-Fi profiles for exposed network credentials +7. Investigate the initial access vector and delivery mechanism +8. Search for LaZagne execution across other endpoints +', '["https://attack.mitre.org/techniques/T1555/","https://github.com/AlessandroZ/LaZagne","https://www.sans.org/blog/detecting-lazagne/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)lazagne\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)lazagne\\\\.exe$") || + regexMatch("log.eventDataCommandLine", "(?i)lazagne\\\\s+(all|browsers|chats|databases|games|git|mails|maven|memory|multimedia|php|svn|sysadmin|wifi|windows)") || + regexMatch("log.eventDataCommandLine", "(?i)lazagne\\\\s+(all|browsers|chats|databases|games|git|mails|maven|memory|multimedia|php|svn|sysadmin|wifi|windows)") || + regexMatch("log.eventDataCommandLine", "(?i)lazagne\\\\.exe") || + regexMatch("log.eventDataCommandLine", "(?i)lazagne\\\\.exe") +) +', '2026-02-23 16:19:26.931122', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1071, 'Keylogger Activity Detection', 3, 2, 0, 'Collection', 'T1056.001 - Input Capture: Keylogging', e'Detects keylogger activity through Windows API hook installation, clipboard monitoring, +keyboard input capture, and known keylogger tool execution. Attackers use keyloggers +to capture user credentials, sensitive data, and communications. + +Next Steps: +1. Immediately isolate the affected host to stop credential capture +2. Identify the source process installing keyboard hooks and its origin +3. Check if the hooking process is a known legitimate application +4. Review what user accounts have been active on the host during the capture period +5. Force password resets for all accounts used on the compromised system +6. Check for data exfiltration - keylog data being sent externally +7. Examine the process tree to find how the keylogger was installed +8. Scan for persistence mechanisms associated with the keylogger +9. Review MFA tokens and session cookies that may have been captured +', '["https://attack.mitre.org/techniques/T1056/001/","https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw","https://www.sans.org/reading-room/whitepapers/detection/detecting-keyloggers-36062"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataCommandLine", "(?i)(GetAsyncKeyState|GetKeyState|GetKeyboardState|SetWindowsHookEx|SetWindowsHookExW|SetWindowsHookExA)") || + regexMatch("log.eventDataCommandLine", "(?i)(keylog|key.?log|key.?stroke|key.?capture|keystroke.?log)") || + regexMatch("log.eventDataCommandLine", "(?i)(Get-Clipboard|Set-Clipboard|clipboard.?monitor|ClipboardChanged)") || + regexMatch("log.eventDataCommandLine", "(?i)(GetAsyncKeyState|GetKeyState|SetWindowsHookEx)") || + regexMatch("log.eventDataCommandLine", "(?i)(keylog|key.?log|key.?stroke|key.?capture)") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + contains("log.eventDataScriptBlockText", "GetAsyncKeyState") || + contains("log.eventDataScriptBlockText", "GetKeyboardState") || + contains("log.eventDataScriptBlockText", "SetWindowsHookEx") || + contains("log.eventDataScriptBlockText", "WH_KEYBOARD_LL") || + contains("log.eventDataScriptBlockText", "WH_KEYBOARD") || + contains("log.eventDataScriptBlockText", "MapVirtualKey") || + contains("log.eventDataScriptBlockText", "Get-Keystrokes") || + contains("log.eventDataScriptBlockText", "Invoke-Keylogger") || + ( + contains("log.eventDataScriptBlockText", "user32.dll") && + ( + contains("log.eventDataScriptBlockText", "GetAsyncKeyState") || + contains("log.eventDataScriptBlockText", "GetForegroundWindow") || + contains("log.eventDataScriptBlockText", "GetWindowText") + ) + ) || + ( + contains("log.eventDataScriptBlockText", "DllImport") && + contains("log.eventDataScriptBlockText", "user32") && + contains("log.eventDataScriptBlockText", "KeyState") + ) || + contains("log.eventDataScriptBlockText", "OpenClipboard") || + contains("log.eventDataScriptBlockText", "GetClipboardData") || + contains("log.eventDataScriptBlockText", "GetAsyncKeyState") || + contains("log.eventDataScriptBlockText", "SetWindowsHookEx") || + contains("log.eventDataScriptBlockText", "WH_KEYBOARD_LL") + ) +) || +( + equals("log.eventCode", "8") && + contains("log.eventDataStartModule", "user32.dll") && + contains("log.eventDataStartFunction", "SetWindowsHookEx") +) +', '2026-02-23 16:19:27.976258', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{origin.host}}"}],"or":null,"within":"now-15m","count":2}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1072, 'Kerberoasting Attack Detection', 3, 2, 1, 'Credential Access', 'T1558.003 - Steal or Forge Kerberos Tickets: Kerberoasting', e'Detects Kerberoasting attacks where adversaries request Kerberos TGS tickets encrypted with RC4 (0x17) for +service accounts in order to crack them offline and obtain plaintext credentials. This is the most common +Active Directory credential theft technique used in real-world compromises. The rule monitors Event ID 4769 +(Kerberos Service Ticket Operations) for RC4 encryption requests while excluding machine accounts (ending in $) +and legitimate system services. + +Next Steps: +1. Identify the requesting user account and verify if this is authorized security testing +2. Check which service account SPNs were targeted for TGS requests +3. Review if the requesting account has been compromised +4. Audit all service accounts with SPNs for weak passwords +5. Consider implementing AES-only Kerberos encryption policies +6. Rotate passwords for targeted service accounts immediately +7. Enable Group Managed Service Accounts (gMSA) where possible +8. Monitor for follow-up lateral movement using obtained credentials +', '["https://attack.mitre.org/techniques/T1558/003/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4769","https://adsecurity.org/?p=2293"]', e'equals("log.eventCode", "4769") && +equals("log.channel", "Security") && +equals("log.eventDataTicketEncryptionType", "0x17") && +!regexMatch("log.eventDataServiceName", "(?i)\\\\$$") && +!equals("log.eventDataServiceName", "krbtgt") && +!oneOf("log.eventDataTicketOptions", ["0x40810000", "0x40800000", "0x40810010"]) && +exists("log.eventDataServiceName") +', '2026-02-23 16:19:29.093231', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{log.eventDataIpAddress}}"}],"or":null,"within":"now-15m","count":3}]', '["lastEvent.log.eventDataIpAddress","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1073, 'Impacket Lateral Movement Detection', 3, 3, 2, 'Lateral Movement', 'T1021.002 - Remote Services: SMB/Windows Admin Shares', e'Detects Impacket framework lateral movement patterns including wmiexec, smbexec, dcomexec, and atexec. +Impacket is the most commonly used lateral movement framework in real-world attacks. The characteristic +pattern involves cmd.exe /Q /c with output redirection to 127.0.0.1 via named pipes or ADMIN$ share, +as well as specific command-line patterns unique to each Impacket module. + +Next Steps: +1. Identify the source of the lateral movement and the compromised credentials used +2. Examine the full command line for data exfiltration or payload delivery +3. Check for Impacket artifacts in the ADMIN$ share or temp directories +4. Review authentication logs for the credential source +5. Isolate affected hosts and block lateral movement paths +6. Reset credentials for all accounts used in the lateral movement +7. Hunt for Impacket usage across all domain-joined systems +8. Review network logs for SMB traffic patterns consistent with Impacket +', '["https://attack.mitre.org/techniques/T1021/002/","https://github.com/fortra/impacket","https://www.13cubed.com/downloads/impacket_exec_commands_cheat_sheet.pdf"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + ( + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/Q\\\\s+/c\\\\s+.+\\\\s+1>\\\\s*\\\\\\\\\\\\\\\\127\\\\.0\\\\.0\\\\.1\\\\\\\\") || + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/Q\\\\s+/c\\\\s+.+\\\\s+1>\\\\s*\\\\\\\\\\\\\\\\127\\\\.0\\\\.0\\\\.1\\\\\\\\") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/Q\\\\s+/c\\\\s+.+\\\\s+2>&1") || + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/Q\\\\s+/c\\\\s+.+\\\\s+2>&1") + ) || + ( + regexMatch("log.eventDataParentProcessName", "(?i)wmiprvse\\\\.exe$") && + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/Q\\\\s+/c") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\__output\\\\s") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\__output\\\\s") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/C\\\\s+.+\\\\\\\\ADMIN\\\\$\\\\\\\\__\\\\d+\\\\.\\\\d+") || + regexMatch("log.eventDataCommandLine", "(?i)cmd\\\\.exe\\\\s+/C\\\\s+.+\\\\\\\\ADMIN\\\\$\\\\\\\\__\\\\d+\\\\.\\\\d+") + ) +) +', '2026-02-23 16:19:30.226586', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1074, 'Image File Execution Options Debugger Persistence', 3, 3, 2, 'Persistence', 'T1546.012 - Event Triggered Execution: Image File Execution Options Injection', e'Detects abuse of Image File Execution Options (IFEO) registry keys to establish persistence +or redirect program execution. Attackers set the Debugger value under IFEO to hijack +execution of legitimate programs (like accessibility tools sethc.exe, utilman.exe, narrator.exe) +and redirect them to cmd.exe or other malicious payloads. This technique is also used for +persistence through GlobalFlag and SilentProcessExit monitoring. + +Next Steps: +1. Check which program\'s IFEO key was modified and what debugger was set +2. Verify if accessibility tool hijacking was used (sethc, utilman, narrator, magnify, osk) +3. Remove the malicious Debugger registry value +4. Check for RDP access if accessibility tools were hijacked (sticky keys attack) +5. Investigate the user account that made the registry modification +6. Search for additional persistence mechanisms +7. Review recent RDP login activity on the affected host +', '["https://attack.mitre.org/techniques/T1546/012/","https://blog.malwarebytes.com/101/2015/12/an-introduction-to-image-file-execution-options/","https://oddvar.moe/2018/04/10/persistence-using-globalflags-in-image-file-execution-options/"]', e'( + equals("log.eventCode", "13") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + regexMatch("log.eventDataTargetObject", "(?i)\\\\\\\\Image File Execution Options\\\\\\\\.*\\\\\\\\(Debugger|GlobalFlag|MonitorProcess)$") && + !regexMatch("log.eventDataDetails", "(?i)(werfault|drwtsn32|vsjitdebugger|windbg)") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*Image File Execution Options.*Debugger") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*Image File Execution Options.*Debugger") + ) +) +', '2026-02-23 16:19:31.386534', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1075, 'GPO Tampering Detection', 2, 3, 2, 'Defense Evasion, Privilege Escalation', 'T1484.001 - Domain Policy Modification: Group Policy Modification', e'Detects modifications to Group Policy Objects (GPOs) which could indicate an adversary attempting to escalate privileges, deploy malware across the domain, or establish persistence. GPO tampering is a common technique used by attackers to enforce malicious configurations or deploy payloads to multiple systems simultaneously. + +**Next Steps:** +1. Immediately review the specific GPO that was modified and identify what changes were made +2. Verify if the modification was authorized by checking with the responsible administrator +3. Examine the user account that made the changes for signs of compromise +4. Review recent authentication logs for the user account to identify potential lateral movement +5. Check domain controllers for additional suspicious activities around the same timeframe +6. If unauthorized, immediately revert the GPO changes and investigate the compromise vector +7. Consider temporarily disabling the affected user account pending investigation +', '["https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing","https://attack.mitre.org/techniques/T1484/001/"]', 'equals("log.eventId", "5136") && equals("log.providerName", "Microsoft-Windows-Security-Auditing") && contains("log.eventDataObjectDN", "CN=Policies,CN=System")', '2026-02-23 16:19:32.544738', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1076, 'Golden Ticket Attack Detection', 3, 3, 3, 'Credential Access', 'T1558.001 - Steal or Forge Kerberos Tickets: Golden Ticket', e'Detects Golden Ticket attacks where adversaries forge Kerberos TGTs using the KRBTGT account +hash, granting unlimited domain access. The rule detects anomalous TGT usage patterns including +TGS requests with unusual encryption types, tickets with abnormally long lifetimes, and Kerberos +authentication from non-domain-controller sources for the KRBTGT service. + +Next Steps: +1. Immediately verify if the KRBTGT account password has been compromised +2. Reset the KRBTGT password TWICE to invalidate all existing tickets +3. Identify the source host and investigate for full domain compromise +4. Review all domain admin activity from the suspected timeframe +5. Check for DCSync or NTDS.dit extraction as precursor activities +6. Audit all privileged account access across the domain +7. Consider rebuilding the domain if compromise is confirmed +8. Implement Kerberos armoring and constrained delegation +', '["https://attack.mitre.org/techniques/T1558/001/","https://adsecurity.org/?p=1640","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4769"]', e'( + equals("log.eventCode", "4769") && + equals("log.channel", "Security") && + equals("log.eventDataServiceName", "krbtgt") && + !equals("log.eventDataStatus", "0x0") && + exists("log.eventDataIpAddress") +) || +( + equals("log.eventCode", "4768") && + equals("log.channel", "Security") && + !oneOf("log.eventDataTicketEncryptionType", ["0x12", "0x11"]) && + exists("target.user") && + !regexMatch("target.user", "(?i)\\\\$$") +) || +( + equals("log.eventCode", "4672") && + equals("log.channel", "Security") && + contains("log.eventDataPrivilegeList", "SeTcbPrivilege") && + !regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|LOCAL SERVICE|NETWORK SERVICE)$") && + !regexMatch("log.eventDataSubjectUserName", "(?i)\\\\$$") +) +', '2026-02-23 16:19:33.705324', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{origin.host}}"}],"or":null,"within":"now-30m","count":3}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1086, 'Credential Dump via Registry Export', 3, 2, 1, 'Credential Access', 'T1003.004 - OS Credential Dumping: LSA Secrets', e'Detects credential dumping via registry export of SAM, SYSTEM, and SECURITY hives using +reg.exe save commands. Attackers export these hives to extract password hashes offline using +tools like secretsdump.py or samdump2. This technique is commonly used after gaining local +administrator privileges. + +Next Steps: +1. Identify the user account executing the reg save commands +2. Check if SAM, SYSTEM, and SECURITY hives were all exported (indicates deliberate extraction) +3. Search for the output files on disk and check if they were exfiltrated +4. Review for follow-up pass-the-hash or credential reuse activity +5. Reset all local account passwords on the affected system +6. Investigate how the attacker obtained local administrator privileges +7. Check for domain credential exposure if SECURITY hive was exported +', '["https://attack.mitre.org/techniques/T1003/004/","https://www.sans.org/blog/protecting-privileged-domain-accounts-safeguarding-password-hashes/","https://pentestlab.blog/2018/07/04/dumping-domain-password-hashes/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + (regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SAM") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SAM")) || + (regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SYSTEM") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SYSTEM")) || + (regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SECURITY") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+(save|export)\\\\s+[\\"\']?HKLM\\\\\\\\SECURITY")) || + (regexMatch("log.eventDataCommandLine", "(?i)esentutl.*/y.*/d.*ntds\\\\.dit") || + regexMatch("log.eventDataCommandLine", "(?i)esentutl.*/y.*/d.*ntds\\\\.dit")) +) +', '2026-02-23 16:19:45.775914', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1095, 'AS-REP Roasting Attack Detection', 3, 2, 1, 'Credential Access', 'T1558.004 - Steal or Forge Kerberos Tickets: AS-REP Roasting', e'Detects AS-REP Roasting attacks targeting accounts with Kerberos pre-authentication disabled. +Attackers request AS-REP messages encrypted with RC4 (0x17) for accounts that do not require +pre-authentication, enabling offline password cracking. This is a companion technique to Kerberoasting +and targets a different set of vulnerable accounts. + +Next Steps: +1. Identify accounts with pre-authentication disabled and evaluate business justification +2. Enable Kerberos pre-authentication on all identified accounts +3. Verify the requesting source IP is not a known attack tool +4. Reset passwords for targeted accounts using strong, complex passwords +5. Audit Active Directory for accounts with DONT_REQUIRE_PREAUTH flag +6. Monitor for subsequent credential usage from the requesting IP +', '["https://attack.mitre.org/techniques/T1558/004/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4768","https://blog.harmj0y.net/activedirectory/roasting-as-reps/"]', e'equals("log.eventCode", "4768") && +equals("log.channel", "Security") && +equals("log.eventDataTicketEncryptionType", "0x17") && +equals("log.eventDataPreAuthType", "0") && +!regexMatch("target.user", "(?i)\\\\$$") && +exists("target.user") +', '2026-02-23 16:19:55.865220', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{log.eventDataIpAddress}}"}],"or":null,"within":"now-15m","count":3}]', '["lastEvent.log.eventDataIpAddress","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1096, 'AppInit DLLs Persistence Detection', 3, 3, 2, 'Persistence', 'T1546.010 - Event Triggered Execution: AppInit DLLs', e'Detects modifications to the AppInit_DLLs registry key, which causes specified DLLs to be +loaded into every process that loads User32.dll. Attackers use this technique for persistence +and DLL injection, as the malicious DLL will be loaded into virtually every user-mode process. +On modern Windows with Secure Boot, this requires LoadAppInit_DLLs to also be enabled. + +Next Steps: +1. Identify the DLL path being added to the AppInit_DLLs registry value +2. Analyze the referenced DLL for malicious functionality +3. Check if LoadAppInit_DLLs was also enabled (required on modern Windows) +4. Remove the malicious DLL path from the registry value +5. Delete the malicious DLL file from disk +6. Reboot the system to stop the DLL from being loaded into new processes +7. Investigate the initial compromise that led to this persistence +', '["https://attack.mitre.org/techniques/T1546/010/","https://docs.microsoft.com/en-us/windows/win32/dlls/secure-boot-and-appinit-dlls","https://pentestlab.blog/2020/01/07/persistence-appinit-dlls/"]', e'( + equals("log.eventCode", "13") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + regexMatch("log.eventDataTargetObject", "(?i)\\\\\\\\Windows\\\\\\\\CurrentVersion\\\\\\\\Windows\\\\\\\\(AppInit_DLLs|LoadAppInit_DLLs)$") && + exists("log.eventDataDetails") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*AppInit_DLLs") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*AppInit_DLLs") + ) +) +', '2026-02-23 16:19:56.893551', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1081, 'DLL Sideloading Detection', 2, 3, 1, 'Defense Evasion', 'T1574.002 - Hijack Execution Flow: DLL Side-Loading', e'Detects DLL sideloading patterns where known vulnerable legitimate applications load malicious +DLLs from non-system paths. Attackers place a malicious DLL alongside a vulnerable application +that loads DLLs from its own directory rather than the system directory. This technique +leverages trusted application signatures to execute malicious code and evade security controls. + +Next Steps: +1. Verify the executable path - legitimate apps should be in Program Files, not user directories +2. Check the DLL loaded alongside the executable for unexpected modifications +3. Compare DLL hashes against known good versions +4. Examine the parent process to understand how the vulnerable app was launched +5. Review file creation timestamps for the executable and DLL pair +6. Analyze the suspicious DLL in a sandbox +7. Search for similar sideloading patterns across other endpoints +', '["https://attack.mitre.org/techniques/T1574/002/","https://www.mandiant.com/resources/blog/dll-side-loading-a-thorn-in-the-side-of-the-anti-virus-industry","https://hijacklibs.net/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + ( + regexMatch("log.eventDataNewProcessName", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Desktop\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") && + regexMatch("log.eventDataNewProcessName", "(?i)(OneDriveStandaloneUpdater|colorcpl|consent|dxcap|eudcedit|eventvwr|isoburn|msconfig|msdt|mstsc|narrator|netplwiz|odbcad32|presentationhost|rstrui|sdclt|sethc|sigverif|utilman|write)\\\\.exe$") + ) || + ( + regexMatch("log.eventDataProcessName", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Desktop\\\\\\\\)") && + regexMatch("log.eventDataProcessName", "(?i)(OneDriveStandaloneUpdater|colorcpl|consent|dxcap|eudcedit|eventvwr|isoburn|msconfig|msdt|mstsc|narrator|netplwiz|odbcad32|presentationhost|rstrui|sdclt|sethc|sigverif|utilman|write)\\\\.exe$") + ) || + ( + regexMatch("log.eventDataNewProcessName", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Desktop\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\)") && + regexMatch("log.eventDataNewProcessName", "(?i)(WerFault|SearchProtocolHost|SearchFilterHost|WmiPrvSE|backgroundTaskHost|RuntimeBroker|smartscreen|tabcal|winsat)\\\\.exe$") + ) +) +', '2026-02-23 16:19:39.810138', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1082, 'DCSync Attack Detection', 3, 3, 1, 'Credential Access', 'T1003.006 - OS Credential Dumping: DCSync', e'Detects DCSync attacks where attackers use directory replication services to retrieve password hashes from domain controllers. +This technique exploits legitimate Active Directory replication functionality to extract credentials without directly accessing the domain controller\'s files. +The rule monitors for specific replication GUIDs associated with credential access operations in Windows Event ID 4662. + +Next Steps: +- Immediately verify the legitimacy of the user account performing the replication operation +- Check if the source host is an authorized domain controller or backup system +- Review recent privilege escalation activities for the identified user account +- Examine network traffic for additional signs of credential harvesting +- Consider resetting passwords for high-privilege accounts if compromise is confirmed +- Review domain controller access logs for unauthorized administrative activities +', '["https://attack.mitre.org/techniques/T1003/006/","https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4662","https://www.elastic.co/guide/en/security/current/potential-credential-access-via-dcsync.html"]', e'equals("log.eventCode", "4662") && +equals("log.channel", "Security") && +equals("log.eventDataObjectServer", "DS") && +( + contains("log.eventDataProperties", "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2") || + contains("log.eventDataProperties", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2") || + contains("log.eventDataProperties", "89e95b76-444d-4c62-991a-0facbeda640c") || + contains("log.eventDataProperties", "19195a5b-6da0-11d0-afd3-00c04fd930c9") +) && +!regexMatch("log.eventDataSubjectUserName", ".*\\\\$$") && +!contains("origin.host", "DC") +', '2026-02-23 16:19:40.983773', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataSubjectUserName","operator":"filter_term","value":"{{log.eventDataSubjectUserName}}"}],"or":null,"within":"now-1h","count":1}]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1083, 'DCShadow Attack Detection', 3, 3, 2, 'Defense Evasion', 'T1207 - Rogue Domain Controller', e'Detects DCShadow attacks where attackers register a rogue domain controller to push malicious Active Directory changes. This technique allows adversaries to modify Active Directory objects by registering a rogue domain controller and triggering replication, effectively bypassing security controls and detection mechanisms. + +The rule monitors for: +- Computer account modifications with domain controller service principal names +- Access to sensitive Active Directory objects and properties +- Creation of server objects in the domain controller configuration + +Next Steps: +1. Immediately investigate the source host and user account involved in the activity +2. Check if the host is an authorized domain controller in your environment +3. Review recent Active Directory changes and replication logs +4. Examine authentication logs for the affected user account +5. Verify the legitimacy of any recent domain controller promotions +6. Check for signs of compromise on the source system +7. Consider isolating the affected host if unauthorized activity is confirmed +8. Review domain controller security policies and access controls +', '["https://attack.mitre.org/techniques/T1207/","https://www.dcshadow.com/","https://blog.alsid.eu/dcshadow-explained-4510f52fc19d"]', e'( + (equals("log.eventCode", "4742") && + equals("log.channel", "Security") && + contains("log.eventDataServicePrincipalNames", "GC/") && + contains("log.eventDataUserAccountControl", "SERVER_TRUST_ACCOUNT")) || + (equals("log.eventCode", "4662") && + equals("log.channel", "Security") && + equals("log.eventDataObjectType", "{bf967a92-0de6-11d0-a285-00aa003049e2}") && + contains("log.eventDataProperties", "1131f6ac-9c07-11d1-f79f-00c04fc2dcd2")) || + (equals("log.eventCode", "5137") && + equals("log.channel", "Security") && + equals("log.eventDataObjectClass", "server") && + contains("log.eventDataObjectDN", "CN=Servers,CN=")) +) && +!contains("origin.host", "DC") +', '2026-02-23 16:19:42.186234', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{origin.host}}"}],"or":null,"within":"now-2h","count":2}]', '["lastEvent.log.eventDataSubjectUserName","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1084, 'DCOM Lateral Movement Detection', 3, 3, 2, 'Lateral Movement', 'T1021.003 - Remote Services: Distributed Component Object Model', e'Detects potential DCOM lateral movement attempts by monitoring for suspicious process creation with DCOM-related command line parameters. Looks for processes with automation embedding flags and specific DCOM object CLSIDs commonly abused for lateral movement such as ShellWindows and MMC20.Application. + +Next Steps: +1. Investigate the source host and user account that initiated the DCOM activity +2. Review network connections between the source and target systems around the time of the alert +3. Check for additional lateral movement indicators on both source and target systems +4. Examine the specific DCOM objects and CLSIDs being accessed for known malicious usage +5. Verify if the DCOM activity aligns with legitimate business processes or scheduled tasks +6. Look for privilege escalation attempts following the lateral movement +7. Check for any data exfiltration or persistence mechanisms deployed post-compromise +', '["https://medium.com/@cY83rR0H1t/detecting-dcom-lateral-movement-ee2b461a8705","https://attack.mitre.org/techniques/T1021/003/"]', 'equals("log.eventCode", "4688") && exists("log.eventDataProcessCommandLine") && (contains("log.eventDataProcessCommandLine", "/automation -Embedding") || contains("log.eventDataProcessCommandLine", "9BA05972-F6A8-11CF-A442-00A0C90A8F39") || contains("log.eventDataProcessCommandLine", "c08afd90-f2a1-11d1-8455-00a0c91f3880") || contains("log.eventDataProcessCommandLine", "MMC20.Application") || contains("log.eventDataProcessCommandLine", "Document.Application.ShellExecute") || contains("log.eventDataProcessCommandLine", "GetTypeFromCLSID") || contains("log.eventDataProcessCommandLine", "GetTypeFromProgID"))', '2026-02-23 16:19:43.365910', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataProcessName","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1085, 'Credential Manager Access Detection', 3, 2, 1, 'Credential Access', 'T1555.004 - Credentials from Password Stores: Windows Credential Manager', e'Detects access to Windows Credential Manager using vaultcmd.exe, cmdkey.exe, or credential +enumeration via PowerShell. Attackers use these tools to extract stored credentials including +web passwords, Windows credentials, and RDP saved logins. This is a common post-exploitation +technique for credential harvesting. + +Next Steps: +1. Identify the user account and process executing credential manager commands +2. Verify if this is authorized administrative activity or penetration testing +3. Review what credentials are stored in the affected user\'s vault +4. Check for follow-up lateral movement using extracted credentials +5. Reset any credentials that may have been exposed +6. Review if remote desktop saved credentials were compromised +7. Investigate the initial access vector that led to credential access +', '["https://attack.mitre.org/techniques/T1555/004/","https://www.passcape.com/index.php?section=docsys&cmd=details&id=28","https://blog.malwarebytes.com/threat-analysis/2020/12/credential-stealing/"]', e'(equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && +( + (regexMatch("log.eventDataCommandLine", "(?i)vaultcmd(.exe)?\\\\s+/(list|listcreds|listproperties)") || + regexMatch("log.eventDataCommandLine", "(?i)vaultcmd(.exe)?\\\\s+/(list|listcreds|listproperties)")) || + (regexMatch("log.eventDataCommandLine", "(?i)cmdkey(.exe)?\\\\s+/list") || + regexMatch("log.eventDataCommandLine", "(?i)cmdkey(.exe)?\\\\s+/list")) || + (regexMatch("log.eventDataCommandLine", "(?i)(CredentialManager|Windows\\\\s+Vault|VaultSvc)") || + regexMatch("log.eventDataCommandLine", "(?i)(CredentialManager|Windows\\\\s+Vault|VaultSvc)")) || + (regexMatch("log.eventDataCommandLine", "(?i)Get-VaultCredential|Get-CachedGPPPassword") || + regexMatch("log.eventDataCommandLine", "(?i)Get-VaultCredential|Get-CachedGPPPassword")) +) +', '2026-02-23 16:19:44.557510', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1087, 'COM Hijacking Persistence Detection', 2, 3, 1, 'Persistence', 'T1546.015 - Event Triggered Execution: Component Object Model Hijacking', e'Detects COM (Component Object Model) hijacking used for persistence by modifying InProcServer32 +or LocalServer32 registry values. Attackers replace legitimate COM object DLLs with malicious ones +to achieve persistence, as the malicious DLL will be loaded whenever the COM object is instantiated. +This is a stealthy persistence mechanism that is difficult to detect without registry monitoring. + +Next Steps: +1. Examine the modified InProcServer32/LocalServer32 value to identify the malicious DLL +2. Verify if the new DLL path points to a legitimate or suspicious location +3. Check the original COM object registration for comparison +4. Analyze the replacement DLL in a sandbox +5. Identify the CLSID being hijacked and what software uses it +6. Restore the original COM registration +7. Search for similar COM hijacking across other endpoints +', '["https://attack.mitre.org/techniques/T1546/015/","https://pentestlab.blog/2020/05/20/persistence-com-hijacking/","https://bohops.com/2018/08/18/abusing-the-com-registry-structure-clsid-localserver32-inprocserver32/"]', e'equals("log.eventCode", "4657") && +equals("log.channel", "Security") && +regexMatch("log.eventDataObjectName", "(?i)(InProcServer32|LocalServer32)") && +( + regexMatch("log.eventDataNewValue", "(?i)(\\\\\\\\Temp\\\\\\\\|\\\\\\\\Downloads\\\\\\\\|\\\\\\\\AppData\\\\\\\\|\\\\\\\\ProgramData\\\\\\\\|\\\\\\\\Users\\\\\\\\Public\\\\\\\\|\\\\\\\\Desktop\\\\\\\\)") || + regexMatch("log.eventDataNewValue", "(?i)(rundll32|regsvr32|mshta|powershell|cmd\\\\.exe|wscript|cscript)") || + regexMatch("log.eventDataNewValue", "(?i)scrobj\\\\.dll") || + regexMatch("log.eventDataNewValue", "(?i)(http://|https://)") || + !regexMatch("log.eventDataNewValue", "(?i)^(C:\\\\\\\\Windows\\\\\\\\|C:\\\\\\\\Program Files)") +) && +!regexMatch("log.eventDataSubjectUserName", "(?i)^(SYSTEM|TrustedInstaller)$") +', '2026-02-23 16:19:46.863971', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1088, 'Cobalt Strike Process Behavior Detection', 3, 3, 2, 'Execution', 'T1059 - Command and Scripting Interpreter', e'Detects Cobalt Strike beacon process creation patterns that are distinct from normal system behavior. +These include rundll32.exe spawned without command-line arguments, dllhost.exe or runonce.exe spawning +cmd.exe or powershell, and characteristic post-exploitation process injection patterns. Cobalt Strike +is the most widely used commercial adversary simulation tool and is frequently found in real attacks. + +Next Steps: +1. Examine the parent-child process relationships for injection patterns +2. Check for rundll32 with no arguments (classic beacon default) +3. Review named pipe activity on the host for CS pipe names +4. Check for network beaconing behavior from the suspicious process +5. Memory scan the suspicious processes for CS beacon shellcode +6. Isolate the affected host immediately +7. Hunt for lateral movement from this host to other systems +8. Review the C2 infrastructure and block at network perimeter +', '["https://attack.mitre.org/software/S0154/","https://thedfirreport.com/2021/08/29/cobalt-strike-a-defenders-guide/","https://redcanary.com/threat-detection-report/threats/cobalt-strike/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + ( + regexMatch("log.eventDataNewProcessName", "(?i)rundll32\\\\.exe$") && + ( + !exists("log.eventDataCommandLine") || + regexMatch("log.eventDataCommandLine", "(?i)^\\"?[A-Z]:\\\\\\\\Windows\\\\\\\\(System32|SysWOW64)\\\\\\\\rundll32\\\\.exe\\"?\\\\s*$") + ) + ) || + ( + regexMatch("log.eventDataParentProcessName", "(?i)(dllhost\\\\.exe|runonce\\\\.exe|searchprotocolhost\\\\.exe)$") && + regexMatch("log.eventDataNewProcessName", "(?i)(cmd\\\\.exe|powershell\\\\.exe|pwsh\\\\.exe)$") + ) || + ( + regexMatch("log.eventDataParentProcessName", "(?i)rundll32\\\\.exe$") && + regexMatch("log.eventDataNewProcessName", "(?i)(cmd\\\\.exe|powershell\\\\.exe|pwsh\\\\.exe)$") && + !exists("log.eventDataParentCommandLine") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)(MSSE-\\\\d+-server|status_\\\\d+|postex_\\\\d+|msagent_\\\\d+)") || + regexMatch("log.eventDataCommandLine", "(?i)(MSSE-\\\\d+-server|status_\\\\d+|postex_\\\\d+|msagent_\\\\d+)") + ) || + ( + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\\\\\\\\\\\\\.\\\\\\\\.pipe\\\\\\\\(MSSE-|postex_|status_|msagent_)") || + regexMatch("log.eventDataCommandLine", "(?i)\\\\\\\\\\\\\\\\\\\\.\\\\\\\\.pipe\\\\\\\\(MSSE-|postex_|status_|msagent_)") + ) +) +', '2026-02-23 16:19:47.995694', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1089, 'CMSTP UAC Bypass Detection', 2, 3, 1, 'Defense Evasion', 'T1218.003 - System Binary Proxy Execution: CMSTP', e'Detects CMSTP.exe (Microsoft Connection Manager Profile Installer) being used to bypass UAC +and AppLocker restrictions. Attackers use CMSTP with specially crafted .inf files containing +malicious commands in the RunPreSetupCommandsSection to execute arbitrary code with elevated +privileges. This is a well-known UAC bypass technique. + +Next Steps: +1. Examine the .inf file referenced in the command line for malicious content +2. Check the RunPreSetupCommandsSection of the INF file for commands +3. Identify the parent process and delivery mechanism +4. Review if UAC was successfully bypassed +5. Check for post-exploitation activity with elevated privileges +6. Remove the malicious INF file and any created artifacts +7. Search for similar CMSTP abuse across other endpoints +', '["https://attack.mitre.org/techniques/T1218/003/","https://lolbas-project.github.io/lolbas/Binaries/Cmstp/","https://msitpros.com/?p=3960"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)cmstp\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)cmstp\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)/s\\\\s+.*\\\\.inf") || + regexMatch("log.eventDataCommandLine", "(?i)/ni\\\\s+/s\\\\s+") || + regexMatch("log.eventDataCommandLine", "(?i)/au\\\\s+") || + regexMatch("log.eventDataCommandLine", "(?i)/s\\\\s+.*\\\\.inf") || + regexMatch("log.eventDataCommandLine", "(?i)/ni\\\\s+/s\\\\s+") || + regexMatch("log.eventDataCommandLine", "(?i)/au\\\\s+") +) +', '2026-02-23 16:19:49.168845', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1090, 'Certutil LOLBIN Abuse Detection', 2, 3, 1, 'Defense Evasion', 'T1105 - Ingress Tool Transfer', e'Detects abuse of certutil.exe as a Living Off The Land Binary (LOLBIN) for downloading files from URLs, +encoding/decoding Base64 payloads, and NTLM coercion. Certutil is a legitimate Windows certificate +utility that is frequently abused by attackers for payload staging and defense evasion because it is +a signed Microsoft binary that bypasses application whitelisting. + +Next Steps: +1. Examine the full command line to identify downloaded URLs or encoded payloads +2. Check the destination file path for downloaded or decoded files +3. Analyze any downloaded files in a sandbox environment +4. Review the parent process to understand how certutil was invoked +5. Check for subsequent execution of downloaded payloads +6. Block the identified download URLs at the proxy/firewall level +7. Search for similar certutil abuse across other endpoints +', '["https://attack.mitre.org/techniques/T1105/","https://attack.mitre.org/techniques/T1140/","https://lolbas-project.github.io/lolbas/Binaries/Certutil/"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)certutil\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)certutil\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)(-urlcache|-URL)") || + regexMatch("log.eventDataCommandLine", "(?i)(-encode|-decode)") || + regexMatch("log.eventDataCommandLine", "(?i)(-ping|-generateSSTFromWU)") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") || + regexMatch("log.eventDataCommandLine", "(?i)-verifyctl") || + regexMatch("log.eventDataCommandLine", "(?i)(-urlcache|-URL)") || + regexMatch("log.eventDataCommandLine", "(?i)(-encode|-decode)") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") +) +', '2026-02-23 16:19:50.309708', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1091, 'Certificate Services Abuse Detection', 3, 3, 1, 'Credential Access', 'T1558 - Steal or Forge Kerberos Tickets', e'Detects suspicious certificate requests and issuance that could indicate Golden Certificate attacks or unauthorized certificate generation for persistence. This rule monitors Windows Certificate Services events for potentially malicious certificate operations, particularly those involving machine accounts or anonymous logons that could be leveraged for persistence and privilege escalation. + +Next Steps: +1. Investigate the certificate request details including the requesting user/machine +2. Verify if the certificate request was legitimate and authorized +3. Check for any recent changes to Certificate Authority policies or templates +4. Review Certificate Authority logs for other suspicious certificate issuance +5. Examine the requesting host for signs of compromise +6. Consider revoking any suspicious certificates issued +7. Validate Certificate Authority security configurations and access controls +', '["https://www.splunk.com/en_us/blog/security/breaking-the-chain-defending-against-certificate-services-abuse.html","https://attack.mitre.org/techniques/T1558/"]', '(equals("log.eventId", "4886") || equals("log.eventId", "4887")) && equals("log.providerName", "Microsoft-Windows-Security-Auditing") && (contains("log.eventDataSubjectUserName", "$") || equals("log.eventDataSubjectUserName", "ANONYMOUS LOGON"))', '2026-02-23 16:19:51.501192', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataSubjectUserName","adversary.host"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1092, 'Boot and Logon Script Persistence Detection', 2, 3, 2, 'Persistence', 'T1037.001 - Boot or Logon Initialization Scripts: Logon Script (Windows)', e'Detects modifications to Windows logon script registry keys including UserInitMprLogonScript, +Userinit, and Shell values. Attackers modify these keys to execute malicious scripts or +binaries during user logon, providing persistent access that survives reboots. These keys +are commonly abused because they execute with the logged-on user\'s privileges. + +Next Steps: +1. Examine the registry value data to identify the script or binary being persisted +2. Analyze the referenced script or executable for malicious content +3. Verify the parent process that modified the registry key +4. Restore the original Userinit or Shell values +5. Remove any malicious scripts from the referenced paths +6. Search for additional persistence mechanisms on the same host +7. Investigate the initial compromise vector +', '["https://attack.mitre.org/techniques/T1037/001/","https://www.cybereason.com/blog/persistence-techniques-that-persist","https://pentestlab.blog/2020/01/14/persistence-logon-scripts/"]', e'( + equals("log.eventCode", "13") && + equals("log.providerName", "Microsoft-Windows-Sysmon") && + ( + regexMatch("log.eventDataTargetObject", "(?i)\\\\\\\\Windows\\\\\\\\CurrentVersion\\\\\\\\(on\\\\\\\\(Userinit|Shell)|Policies\\\\\\\\Explorer\\\\\\\\Run)") || + regexMatch("log.eventDataTargetObject", "(?i)UserInitMprLogonScript$") || + regexMatch("log.eventDataTargetObject", "(?i)\\\\\\\\Environment\\\\\\\\UserInitMprLogonScript$") + ) && + !regexMatch("log.eventDataDetails", "(?i)^C:\\\\\\\\Windows\\\\\\\\system32\\\\\\\\userinit\\\\.exe,?\\\\s*$") && + !regexMatch("log.eventDataDetails", "(?i)^explorer\\\\.exe\\\\s*$") +) || +( + (equals("log.eventCode", "4688") || equals("log.eventCode", "1")) && + ( + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*on.*(Userinit|Shell)") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*on.*(Userinit|Shell)") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*UserInitMprLogonScript") || + regexMatch("log.eventDataCommandLine", "(?i)reg(.exe)?\\\\s+add.*UserInitMprLogonScript") + ) +) +', '2026-02-23 16:19:52.609140', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1093, 'BloodHound Reconnaissance Activity', 3, 2, 1, 'Discovery', 'T1087 - Account Discovery', e'Detects potential BloodHound Active Directory reconnaissance tool usage through LDAP queries, characteristic patterns, and AD enumeration activities. BloodHound is commonly used by attackers to map Active Directory relationships and identify privilege escalation paths. + +Next Steps: +1. Investigate the source host and user account involved in the activity +2. Review network logs for LDAP queries to domain controllers around the same timeframe +3. Check for other reconnaissance tools or suspicious PowerShell activity on the same host +4. Examine Active Directory audit logs for unusual object access patterns +5. Verify if the user account has legitimate reasons for AD enumeration activities +6. Look for signs of lateral movement or privilege escalation following this reconnaissance +7. Consider isolating the affected host if malicious activity is confirmed +', '["https://attack.mitre.org/techniques/T1087/","https://bloodhound.readthedocs.io/","https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662"]', e'( + (equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && + ( + regexMatch("log.eventDataNewProcessName", "(?i)bloodhound") || + regexMatch("log.eventDataNewProcessName", "(?i)sharphound") || + regexMatch("log.eventDataCommandLine", "(?i)(bloodhound|sharphound)") || + regexMatch("log.eventDataCommandLine", "(?i)--CollectionMethod\\\\s+(All|Session|LoggedOn)") || + regexMatch("log.eventDataCommandLine", "(?i)(DCOnly|ComputerOnly|LocalGroup)") + ) +) || +( + (equals("log.eventCode", "4104") || equals("log.eventId", 4104)) && + equals("log.providerName", "Microsoft-Windows-PowerShell") && + ( + regexMatch("log.eventDataScriptBlockText", "(?i)invoke-bloodhound") || + contains("log.eventDataScriptBlockText", "Get-BloodHoundData") || + contains("log.eventDataScriptBlockText", "Get-NetSession") || + contains("log.eventDataScriptBlockText", "Get-NetLoggedOn") || + contains("log.eventDataScriptBlockText", "Get-DomainTrust") + ) +) || +( + equals("log.eventCode", "4662") && + regexMatch("log.eventDataObjectType", "(?i)(bf967aba-0de6-11d0-a285-00aa003049e2|bf967a9c-0de6-11d0-a285-00aa003049e2)") && + oneOf("log.eventDataAccessMask", ["0x100", "0x10000"]) +) || +( + equals("log.eventCode", "5156") && + equals("log.eventDataDestinationPort", "389") && + equals("log.eventDataDirection", "%%14592") +) +', '2026-02-23 16:19:53.756404', true, true, 'origin', '["adversary.host","lastEvent.log.eventDataSubjectUserName"]', '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"origin.host","operator":"filter_term","value":"{{origin.host}}"}],"or":null,"within":"now-2h","count":10}]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1094, 'BITSAdmin Abuse Detection', 2, 3, 1, 'Defense Evasion', 'T1197 - BITS Jobs', e'Detects abuse of bitsadmin.exe for downloading files from remote URLs, creating persistent BITS +jobs, and transferring payloads. BITS (Background Intelligent Transfer Service) is a Windows +service commonly abused by attackers for both download and persistence because BITS jobs survive +reboots and can be configured to execute commands upon completion. + +Next Steps: +1. Examine the download URL and destination path in the command line +2. Review the BITS job for any notification command (persistence mechanism) +3. Check the downloaded file for malicious content +4. List all BITS jobs on the system using \'bitsadmin /list /allusers /verbose\' +5. Remove suspicious BITS jobs and quarantine downloaded files +6. Block the download URL at the proxy/firewall level +7. Search for similar BITS abuse across other endpoints +', '["https://attack.mitre.org/techniques/T1197/","https://lolbas-project.github.io/lolbas/Binaries/Bitsadmin/","https://isc.sans.edu/diary/Investigating+Microsoft+BITS+Activity/23281"]', e'(equals("log.eventCode", "4688") || equals("log.eventId", 4688)) && +( + regexMatch("log.eventDataNewProcessName", "(?i)bitsadmin\\\\.exe$") || + regexMatch("log.eventDataProcessName", "(?i)bitsadmin\\\\.exe$") +) && +( + regexMatch("log.eventDataCommandLine", "(?i)(/transfer|/addfile|/resume|/create|/setnotifycmdline|/setnotifyflags)") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") || + regexMatch("log.eventDataCommandLine", "(?i)/SetMinRetryDelay") || + regexMatch("log.eventDataCommandLine", "(?i)(/transfer|/addfile|/resume|/create|/setnotifycmdline)") || + regexMatch("log.eventDataCommandLine", "(?i)(http://|https://|ftp://)") +) +', '2026-02-23 16:19:54.877359', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1097, 'AMSI Bypass Detection', 3, 3, 2, 'Defense Evasion', 'T1562.001 - Impair Defenses: Disable or Modify Tools', e'Detects attempts to bypass the Antimalware Scan Interface (AMSI) through PowerShell commands, DLL hijacking, or memory patching techniques. AMSI bypass is commonly used by attackers to evade detection when executing malicious PowerShell scripts or other code. + +Next Steps: +1. Immediately isolate the affected host to prevent lateral movement +2. Examine the full PowerShell script block text in Event ID 4104 for malicious content +3. Review command line arguments in Event ID 4688 for suspicious PowerShell execution +4. Check for additional indicators of compromise on the host +5. Verify if legitimate administrative tools are being used or if this is malicious activity +6. Review recent file modifications and process execution history +7. Check for persistence mechanisms that may have been installed +8. Consider reimaging the system if compromise is confirmed +', '["https://attack.mitre.org/techniques/T1562/001/","https://docs.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal"]', e'(equals("log.eventId", "4104") && + ((contains("log.eventDataScriptBlockText", "[Ref].Assembly.GetType") && + contains("log.eventDataScriptBlockText", "amsi") && + contains("log.eventDataScriptBlockText", "SetValue")) || + contains("log.eventDataScriptBlockText", "AmsiUtils") || + contains("log.eventDataScriptBlockText", "amsiInitFailed") || + contains("log.eventDataScriptBlockText", "Bypass.AMSI") || + contains("log.eventDataScriptBlockText", "AmsiScanBuffer"))) || +(equals("log.eventId", "4688") && + contains("log.eventDataCommandLine", "powershell") && + (contains("log.eventDataCommandLine", "amsi.dll") || + contains("log.eventDataCommandLine", "AmsiScanBuffer") || + contains("log.eventDataCommandLine", "amsiInitFailed"))) +', '2026-02-23 16:19:57.999926', true, true, 'origin', null, '[]', '["adversary.host","adversary.user"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1098, 'AdminSDHolder Abuse Detection', 3, 3, 2, 'Persistence, Privilege Escalation', 'T1098 - Account Manipulation', e'Detects modifications to the AdminSDHolder object which can be used for persistence by granting elevated privileges. The SDProp process propagates these permissions to protected groups every 60 minutes, making this a critical security event. + +Next Steps: +1. Immediately review the user account that performed the modification +2. Check if the modification was authorized and part of legitimate administrative activities +3. Examine the specific permissions that were changed on the AdminSDHolder object +4. Monitor for privilege escalation activities in the next 60 minutes (SDProp cycle) +5. Review all members of protected groups for unauthorized additions +6. Audit recent administrative activities by the same user account +7. Consider temporarily disabling the user account if unauthorized activity is suspected +', '["https://attack.mitre.org/techniques/T1098/","https://adsecurity.org/?p=1906","https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-c--protected-accounts-and-groups-in-active-directory"]', e'oneOf("log.eventCode", ["4662", "5136", "4670"]) && +equals("log.channel", "Security") && +( + contains("log.eventDataObjectName", "CN=AdminSDHolder,CN=System") || + contains("log.eventDataObjectDN", "CN=AdminSDHolder,CN=System") +) && +( + oneOf("log.eventDataOperationType", ["Object Access", "Write Property"]) || + oneOf("log.eventDataAccessMask", ["0x20000", "0x40000", "0x80000"]) || + regexMatch("log.action", ".*Permissions.*changed.*") +) && +!equals("log.eventDataSubjectUserName", "SYSTEM") +', '2026-02-23 16:19:59.099681', true, true, 'origin', null, '[]', '["lastEvent.log.eventDataObjectName","lastEvent.log.eventDataSubjectUserName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1099, 'ADFS Authentication Anomalies', 3, 2, 2, 'Defense Evasion, Persistence, Privilege Escalation, Initial Access', 'T1078 - Valid Accounts', e'Detects anomalous authentication attempts against Active Directory Federation Services (ADFS) including multiple failed attempts that could indicate password spraying or brute force attacks. This rule monitors for authentication failures, token validation failures, and other ADFS security events that may indicate malicious activity. + +Next Steps: +1. Review the source IP address and determine if it\'s from a known/trusted location +2. Check for patterns of failed authentication attempts across multiple users +3. Examine ADFS audit logs for additional context around the authentication failures +4. Verify if the targeted user accounts are valid and active +5. Consider implementing IP-based blocking if malicious activity is confirmed +6. Review ADFS configuration for security hardening opportunities +7. Correlate with other authentication events across the domain +', '["https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/troubleshooting/ad-fs-tshoot-logging","https://attack.mitre.org/techniques/T1078/"]', 'equals("log.providerName", "AD FS") && (equals("log.eventId", "411") || equals("log.eventId", "342") || equals("log.eventId", "516")) && contains("log.message", "token validation failed")', '2026-02-23 16:20:00.102814', true, true, 'origin', null, '[{"indexPattern":"v11-log-wineventlog-*","with":[{"field":"log.eventDataIpAddress","operator":"filter_term","value":"{{log.eventDataIpAddress}}"}],"or":null,"within":"now-10m","count":10}]', '["adversary.ip","target.user"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260223/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260223/utm_group_rules_data_type.sql new file mode 100644 index 000000000..2923b3541 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260223/utm_group_rules_data_type.sql @@ -0,0 +1,221 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1015, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1016, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1017, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1018, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1048, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1049, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1050, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1051, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1052, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1053, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1054, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1055, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1056, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1057, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1058, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1059, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1060, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1061, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1062, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1063, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1064, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1065, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1077, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1078, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1079, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1080, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1081, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (879, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (880, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (881, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (882, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (883, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (884, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (885, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (886, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (887, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (888, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (889, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (890, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (891, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (892, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (893, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (894, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (895, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (896, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (897, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (898, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (899, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (900, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (901, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (902, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (903, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (979, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (980, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (981, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (982, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (983, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (984, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (985, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (986, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (987, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (988, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (989, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (990, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (991, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (992, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (993, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (994, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (995, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (996, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (997, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (998, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (999, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1000, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1001, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1002, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1003, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1004, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1005, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1006, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1007, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1008, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1009, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1010, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1011, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1012, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1013, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1014, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1019, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1020, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1021, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1022, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1023, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1024, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1066, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1067, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1068, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1069, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1070, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1071, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1072, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1073, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1074, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1075, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1076, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1082, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1083, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1084, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1085, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1086, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1087, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1088, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1089, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1090, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1091, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1092, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1093, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1094, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1095, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1096, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1097, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1098, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1099, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (904, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (905, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (906, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (907, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (908, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (909, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (910, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (911, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (912, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (913, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (914, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (915, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (916, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (917, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (918, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (919, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (920, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (921, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (922, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (923, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (924, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (925, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (926, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (927, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (928, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (929, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (930, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (931, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (932, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (933, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (934, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (935, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (936, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (937, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (938, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (939, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (940, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (941, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (942, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (943, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (944, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (945, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (946, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (947, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (948, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (949, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (950, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (951, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (952, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (953, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (954, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (955, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (956, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (957, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (958, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (959, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (960, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (961, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (962, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (963, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (964, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (965, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (966, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (967, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (968, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (969, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (970, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (971, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (972, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (973, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (974, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (975, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (976, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (977, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (978, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1025, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1026, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1027, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1028, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1029, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1030, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1031, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1032, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1033, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1034, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1035, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1036, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1037, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1038, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1039, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1040, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1041, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1042, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1043, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1044, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1045, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1046, 1, null); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1047, 1, null); From 3a660fd210ee6d32ab965af866e01f39ae450a7d Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 23 Feb 2026 10:35:38 -0600 Subject: [PATCH 122/240] feat(winevent): add updates for winevent correlation rules and filter --- .../20260223001_update_filter_winevent.xml | 2995 +++++++++++++++++ .../resources/config/liquibase/master.xml | 2 + 2 files changed, 2997 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml b/backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml new file mode 100644 index 000000000..14d99dc73 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml @@ -0,0 +1,2995 @@ + + + + + + + + + + \ No newline at end of file diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index b9fbb258a..7a609ec65 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -469,6 +469,8 @@ + + From 58314204d77d6b42c2851d6972352381b320713f Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 23 Feb 2026 11:20:41 -0600 Subject: [PATCH 123/240] feat(winevent): update filter version and rename log fields for improved clarity --- .../20260223001_update_filter_winevent.xml | 505 ++++++++---------- 1 file changed, 222 insertions(+), 283 deletions(-) diff --git a/backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml b/backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml index 14d99dc73..3fbc5da66 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20260223001_update_filter_winevent.xml @@ -10,9 +10,9 @@ Date: Mon, 23 Feb 2026 13:22:01 -0600 Subject: [PATCH 124/240] feat(visualizations): update Windows visualizations to align with logstash filter v3.1.0 field transformations --- ...60223003_update_windows_visualizations.xml | 407 ++++++++++++++++++ ...60223004_update_windows_visualizations.xml | 86 ++++ .../resources/config/liquibase/master.xml | 5 + 3 files changed, 498 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260223003_update_windows_visualizations.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260223004_update_windows_visualizations.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260223003_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260223003_update_windows_visualizations.xml new file mode 100644 index 000000000..410d2b6b0 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260223003_update_windows_visualizations.xml @@ -0,0 +1,407 @@ + + + + + Update Windows visualizations to match logstash filter v3.1.0 field transformations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260223004_update_windows_visualizations.xml b/backend/src/main/resources/config/liquibase/changelog/20260223004_update_windows_visualizations.xml new file mode 100644 index 000000000..59f6b2c2e --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260223004_update_windows_visualizations.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.winlog.recordNumber.keyword"', '"log.recordNumber.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.winlog.recordNumber.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam11.keyword"', '"log.eventDataParam11.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam11.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam12.keyword"', '"log.eventDataParam12.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam12.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam13.keyword"', '"log.eventDataParam13.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam13.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam17.keyword"', '"log.eventDataParam17.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam17.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam20.keyword"', '"log.eventDataParam20.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam20.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam3.keyword"', '"log.eventDataParam3.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam3.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam4.keyword"', '"log.eventDataParam4.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam4.keyword%' + AND system_owner = true; + + + + + UPDATE utm_visualization + SET aggregation = REPLACE(aggregation::text, '"log.eventDataparam8.keyword"', '"log.eventDataParam8.keyword"')::jsonb + WHERE aggregation::text LIKE '%log.eventDataparam8.keyword%' + AND system_owner = true; + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 7a609ec65..8603507cc 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -473,4 +473,9 @@ + + + + + From 1ed34c0aa332a5a1b0ef1a69e02bb4be62b735f6 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 23 Feb 2026 10:36:55 -0600 Subject: [PATCH 125/240] feat(import): disable back button during loading and fix spacing in upload error message Signed-off-by: Manuel Abascal --- .../app-rule/components/import-rules/import-rule.component.html | 1 + .../utm/util/utm-file-upload/utm-file-upload.component.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/rule-management/app-rule/components/import-rules/import-rule.component.html b/frontend/src/app/rule-management/app-rule/components/import-rules/import-rule.component.html index 0665d74b3..93149ae62 100644 --- a/frontend/src/app/rule-management/app-rule/components/import-rules/import-rule.component.html +++ b/frontend/src/app/rule-management/app-rule/components/import-rules/import-rule.component.html @@ -210,6 +210,7 @@
From 137afb1346735613f365344a173d619bd506fdf0 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 23 Feb 2026 14:37:26 -0600 Subject: [PATCH 126/240] feat(idp): enhance metadata URL validation with improved error handling and encryption key checks --- .../idp_provider/IdentityProviderService.java | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java b/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java index 19f404b76..db8d38ce0 100644 --- a/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java +++ b/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java @@ -1,6 +1,7 @@ package com.park.utmstack.service.idp_provider; +import com.park.utmstack.config.Constants; import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; import com.park.utmstack.repository.idp_provider.IdentityProviderConfigRepository; import com.park.utmstack.service.dto.idp_provider.dto.*; @@ -18,11 +19,11 @@ import java.io.IOException; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URL; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -39,6 +40,13 @@ public List getAllActiveProviders() { public IdentityProviderConfigResponseDto create(IdentityProviderCreateConfigDto dto) { validateMetadataUrl(dto.getMetadataUrl()); + + String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); + if (encryptionKey == null || encryptionKey.isBlank()) { + throw new IllegalStateException( + "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); + } + IdentityProviderConfig entity = mapper.toEntity(dto); entity.setCreatedAt(LocalDateTime.now()); entity.setUpdatedAt(LocalDateTime.now()); @@ -65,7 +73,12 @@ public IdentityProviderConfigResponseDto update(Long id, IdentityProviderConfigR if(dto instanceof IdentityProviderCreateConfigDto createDto){ if (createDto.getSpPrivateKeyPem() != null) { - String encryptedKey = CipherUtil.encrypt(createDto.getSpPrivateKeyPem(), System.getenv("ENCRYPTION_KEY")); + String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); + if (encryptionKey == null || encryptionKey.isBlank()) { + throw new IllegalStateException( + "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); + } + String encryptedKey = CipherUtil.encrypt(createDto.getSpPrivateKeyPem(), encryptionKey); existing.setSpPrivateKeyPem(encryptedKey); } if (createDto.getSpCertificatePem() != null) { @@ -117,10 +130,20 @@ private void validateMetadataUrl(String metadataUrl) { int responseCode = connection.getResponseCode(); if (responseCode != 200) { - throw new SamlMetadataUrlInvalidException("Metadata URL is not accessible"); + throw new SamlMetadataUrlInvalidException( + String.format("Metadata URL is not accessible. HTTP Status: %d", responseCode)); } + + connection.disconnect(); + } catch (MalformedURLException e) { + throw new SamlMetadataUrlInvalidException( + "Invalid metadata URL format: " + e.getMessage()); } catch (IOException e) { - throw new SamlMetadataUrlInvalidException("Failed to access metadata URL"); + throw new SamlMetadataUrlInvalidException( + "Failed to access metadata URL: " + e.getMessage()); + } catch (Exception e) { + throw new SamlMetadataUrlInvalidException( + "Unexpected error validating metadata URL: " + e.getMessage()); } } From 4a609e891ee1f3c0c2be0933988d44a098ffcd00 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 23 Feb 2026 14:44:48 -0600 Subject: [PATCH 127/240] feat(idp): refactor encryption key handling with dedicated validation method --- ...amlRelyingPartyRegistrationRepository.java | 21 +++++++--- .../idp_provider/IdentityProviderService.java | 42 +++++++++++-------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java index d1dd4b13e..4e9c778fd 100644 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java @@ -63,13 +63,24 @@ private void loadProviders(IdentityProviderConfigRepository jpaProviderRepositor } } + /** + * Validates and retrieves the encryption key from environment variables. + * + * @return The validated encryption key + * @throws IllegalStateException if ENCRYPTION_KEY is not configured + */ + private String getValidatedEncryptionKey() { + String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); + if (encryptionKey == null || encryptionKey.isBlank()) { + throw new IllegalStateException( + "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); + } + return encryptionKey; + } + private RelyingPartyRegistration buildRelyingPartyRegistration(IdentityProviderConfig entity) { try { - String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); - if (encryptionKey == null || encryptionKey.isBlank()) { - throw new IllegalStateException( - "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); - } + String encryptionKey = getValidatedEncryptionKey(); String decryptedKey = CipherUtil.decrypt(entity.getSpPrivateKeyPem(), encryptionKey); PrivateKey spKey = PemUtils.parsePrivateKey(decryptedKey); diff --git a/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java b/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java index db8d38ce0..fa67a2e30 100644 --- a/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java +++ b/backend/src/main/java/com/park/utmstack/service/idp_provider/IdentityProviderService.java @@ -22,7 +22,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.time.LocalDateTime; -import java.util.List; import java.util.Optional; @Service @@ -33,19 +32,12 @@ public class IdentityProviderService { private final IdentityProviderConfigRepository repository; private final ApplicationEventPublisher publisher; - public List getAllActiveProviders() { - return repository.findAllByActiveTrue(); - } - public IdentityProviderConfigResponseDto create(IdentityProviderCreateConfigDto dto) { validateMetadataUrl(dto.getMetadataUrl()); - String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); - if (encryptionKey == null || encryptionKey.isBlank()) { - throw new IllegalStateException( - "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); - } + // Validate encryption key before mapper encrypts the private key + getValidatedEncryptionKey(); IdentityProviderConfig entity = mapper.toEntity(dto); entity.setCreatedAt(LocalDateTime.now()); @@ -73,11 +65,7 @@ public IdentityProviderConfigResponseDto update(Long id, IdentityProviderConfigR if(dto instanceof IdentityProviderCreateConfigDto createDto){ if (createDto.getSpPrivateKeyPem() != null) { - String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); - if (encryptionKey == null || encryptionKey.isBlank()) { - throw new IllegalStateException( - "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); - } + String encryptionKey = getValidatedEncryptionKey(); String encryptedKey = CipherUtil.encrypt(createDto.getSpPrivateKeyPem(), encryptionKey); existing.setSpPrivateKeyPem(encryptedKey); } @@ -116,14 +104,30 @@ public void delete(Long id) { repository.deleteById(id); } + /** + * Validates and retrieves the encryption key from environment variables. + * + * @return The validated encryption key + * @throws IllegalStateException if ENCRYPTION_KEY is not configured + */ + private String getValidatedEncryptionKey() { + String encryptionKey = System.getenv(Constants.ENV_ENCRYPTION_KEY); + if (encryptionKey == null || encryptionKey.isBlank()) { + throw new IllegalStateException( + "Environment variable " + Constants.ENV_ENCRYPTION_KEY + " not configured"); + } + return encryptionKey; + } + private void validateMetadataUrl(String metadataUrl) { if (metadataUrl == null || metadataUrl.trim().isEmpty()) { throw new SamlMetadataUrlInvalidException("Metadata URL is required"); } + HttpURLConnection connection = null; try { URL url = new URL(metadataUrl); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(5000); connection.setReadTimeout(5000); @@ -133,8 +137,6 @@ private void validateMetadataUrl(String metadataUrl) { throw new SamlMetadataUrlInvalidException( String.format("Metadata URL is not accessible. HTTP Status: %d", responseCode)); } - - connection.disconnect(); } catch (MalformedURLException e) { throw new SamlMetadataUrlInvalidException( "Invalid metadata URL format: " + e.getMessage()); @@ -144,6 +146,10 @@ private void validateMetadataUrl(String metadataUrl) { } catch (Exception e) { throw new SamlMetadataUrlInvalidException( "Unexpected error validating metadata URL: " + e.getMessage()); + } finally { + if (connection != null) { + connection.disconnect(); + } } } From 8d45e5b5b7f5dd2a038508f96e821e6edf82cbd4 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 23 Feb 2026 16:34:33 -0600 Subject: [PATCH 128/240] feat(saml): implement SAML metadata fetching and provider loading with timeout handling --- .../config/saml/SamlMetadataFetcher.java | 155 ++++++++++++++++ .../config/saml/SamlProvidersLoader.java | 167 ++++++++++++++++++ .../config/saml/SamlRegistrationBuilder.java | 124 +++++++++++++ ...amlRelyingPartyRegistrationRepository.java | 63 ++----- 4 files changed, 462 insertions(+), 47 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java create mode 100644 backend/src/main/java/com/park/utmstack/config/saml/SamlProvidersLoader.java create mode 100644 backend/src/main/java/com/park/utmstack/config/saml/SamlRegistrationBuilder.java diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java new file mode 100644 index 000000000..32d0eec49 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java @@ -0,0 +1,155 @@ +package com.park.utmstack.config.saml; + +import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.concurrent.*; + +/** + * Responsible for fetching SAML metadata with timeout handling. + * Separates the concern of async metadata fetching from registration building. + */ +@Slf4j +public class SamlMetadataFetcher { + + private static final Duration METADATA_FETCH_TIMEOUT = Duration.ofSeconds(10); + + /** + * Fetches SAML metadata with timeout protection. + * Returns null if timeout, error, or interruption occurs. + * Logs detailed error information instead of throwing exceptions. + * + * @param entity Provider configuration + * @return RelyingPartyRegistration, or null if fetch fails + */ + public RelyingPartyRegistration fetchMetadataWithTimeout(IdentityProviderConfig entity) { + ExecutorService timeoutExecutor = null; + Future future = null; + + try { + timeoutExecutor = createMetadataFetchExecutor(entity); + + future = CompletableFuture.supplyAsync(() -> { + try { + return RelyingPartyRegistrations + .fromMetadataLocation(entity.getMetadataUrl()) + .registrationId(entity.getName()) + .build(); + } catch (Exception e) { + throw new CompletionException(e); + } + }, timeoutExecutor); + + return future.get(METADATA_FETCH_TIMEOUT.getSeconds(), TimeUnit.SECONDS); + + } catch (TimeoutException e) { + handleTimeoutException(entity, future, e); + return null; + + } catch (ExecutionException e) { + handleExecutionException(entity, e); + return null; + + } catch (InterruptedException e) { + handleInterruptedException(entity, e); + return null; + + } finally { + cleanupExecutor(entity, timeoutExecutor); + } + } + + /** + * Creates an executor for metadata fetching with proper naming and exception handling. + */ + private ExecutorService createMetadataFetchExecutor(IdentityProviderConfig entity) { + return Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r); + t.setName("saml-metadata-fetch-" + entity.getName()); + t.setDaemon(true); + t.setUncaughtExceptionHandler((thread, throwable) -> + log.error("Uncaught exception in SAML metadata fetch thread for {}: {}", + entity.getName(), throwable.getMessage(), throwable) + ); + return t; + }); + } + + /** + * Handles timeout exception with detailed logging. + */ + private void handleTimeoutException(IdentityProviderConfig entity, Future future, TimeoutException e) { + if (future != null) { + future.cancel(true); + } + log.error( + "SAML metadata fetch TIMEOUT: Provider='{}', Timeout={}s, MetadataUrl='{}'. " + + "This provider will not be available for SSO until it responds faster or the endpoint is fixed.", + entity.getName(), + METADATA_FETCH_TIMEOUT.getSeconds(), + entity.getMetadataUrl(), + e + ); + } + + /** + * Handles execution exception with root cause extraction. + */ + private void handleExecutionException(IdentityProviderConfig entity, ExecutionException e) { + Throwable rootCause = e.getCause() != null ? e.getCause() : e; + log.error( + "SAML metadata fetch FAILED: Provider='{}'. Root cause: {}. " + + "Error details: {}. This provider will not be available for SSO.", + entity.getName(), + rootCause.getClass().getSimpleName(), + rootCause.getMessage(), + rootCause + ); + } + + /** + * Handles interruption exception. + */ + private void handleInterruptedException(IdentityProviderConfig entity, InterruptedException e) { + Thread.currentThread().interrupt(); + log.error( + "SAML metadata fetch INTERRUPTED: Provider='{}'. " + + "Current thread was interrupted. Thread status restored. " + + "This provider will not be available for SSO.", + entity.getName(), + e + ); + } + + /** + * Safely shuts down the executor and logs any issues. + */ + private void cleanupExecutor(IdentityProviderConfig entity, ExecutorService executor) { + if (executor != null) { + try { + executor.shutdownNow(); + + if (!executor.awaitTermination(2, TimeUnit.SECONDS)) { + log.warn( + "Executor for SAML provider '{}' did not terminate cleanly within 2 seconds. " + + "Potential thread leak detected.", + entity.getName() + ); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error( + "Interrupted while waiting for executor shutdown for SAML provider '{}'. " + + "Thread status restored.", + entity.getName(), + e + ); + } + } + } +} + diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlProvidersLoader.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlProvidersLoader.java new file mode 100644 index 000000000..58dbdac75 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlProvidersLoader.java @@ -0,0 +1,167 @@ +package com.park.utmstack.config.saml; + +import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; + +import java.time.Duration; +import java.util.*; +import java.util.concurrent.*; + +/** + * Responsible for loading multiple SAML providers concurrently. + * Separates the concern of concurrent loading and resource management. + */ +@Slf4j +public class SamlProvidersLoader { + + private static final Duration GLOBAL_LOADING_TIMEOUT = Duration.ofSeconds(30); + private static final int MAX_CONCURRENT_LOADS = 5; + + private final SamlRegistrationBuilder registrationBuilder; + + public SamlProvidersLoader(SamlRegistrationBuilder registrationBuilder) { + this.registrationBuilder = registrationBuilder; + } + + /** + * Loads multiple providers concurrently with global timeout. + * Returns a map of loaded registrations (some may be missing if they failed). + * + * @param activeProviders List of provider configurations to load + * @return Map of provider type -> registration (only successful loads) + */ + public Map loadProvidersAsync( + List activeProviders) { + + if (activeProviders.isEmpty()) { + log.info("No active SAML providers found"); + return new ConcurrentHashMap<>(); + } + + log.info("Starting async load of {} SAML provider(s)...", activeProviders.size()); + + ExecutorService executor = null; + try { + executor = createExecutor(activeProviders.size()); + final ExecutorService finalExecutor = executor; + Map registrations = new ConcurrentHashMap<>(); + + List> futures = activeProviders.stream() + .map(entity -> loadProviderAsync(entity, registrations, finalExecutor)) + .toList(); + + waitForAllLoads(futures); + logLoadingResults(activeProviders.size(), registrations.size()); + + return registrations; + + } finally { + shutdownExecutorGracefully(executor); + } + } + + /** + * Creates a thread pool executor for concurrent provider loading. + */ + private ExecutorService createExecutor(int providerCount) { + int poolSize = Math.min(MAX_CONCURRENT_LOADS, providerCount); + return Executors.newFixedThreadPool( + poolSize, + r -> { + Thread t = new Thread(r); + t.setName("saml-provider-loader"); + t.setDaemon(true); + t.setUncaughtExceptionHandler((thread, throwable) -> + log.error("Uncaught exception in provider loading thread: {}", + throwable.getMessage(), throwable) + ); + return t; + } + ); + } + + /** + * Loads a single provider asynchronously. + */ + private CompletableFuture loadProviderAsync( + IdentityProviderConfig entity, + Map registrations, + ExecutorService executor) { + + return CompletableFuture.runAsync(() -> { + try { + RelyingPartyRegistration registration = registrationBuilder.buildRegistration(entity); + + if (registration != null) { + registrations.put(entity.getProviderType().name().toLowerCase(), registration); + log.info("Successfully loaded SAML provider: {} (type: {})", + entity.getName(), entity.getProviderType()); + } else { + log.warn("SAML provider '{}' (type: {}) skipped - unable to load registration. " + + "SSO will not be available for this provider type.", + entity.getName(), entity.getProviderType()); + } + } catch (Exception e) { + log.error("Unexpected error loading SAML provider '{}': {}", + entity.getName(), e.getMessage(), e); + } + }, executor); + } + + /** + * Waits for all provider loads to complete with global timeout. + */ + private void waitForAllLoads(List> futures) { + try { + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .get(GLOBAL_LOADING_TIMEOUT.getSeconds(), TimeUnit.SECONDS); + } catch (TimeoutException e) { + log.warn("Provider loading exceeded global timeout of {}s", + GLOBAL_LOADING_TIMEOUT.getSeconds()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Provider loading was interrupted"); + } catch (ExecutionException e) { + log.error("Error during provider loading: {}", e.getMessage()); + } + } + + /** + * Logs summary of loading results. + */ + private void logLoadingResults(int totalProviders, int loadedCount) { + int failedCount = totalProviders - loadedCount; + log.info("SAML provider loading completed: {} loaded, {} failed, {} total", + loadedCount, failedCount, totalProviders); + } + + /** + * Safely shuts down the executor with proper timeout handling. + */ + private void shutdownExecutorGracefully(ExecutorService executor) { + if (executor == null) { + return; + } + + try { + executor.shutdown(); + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + log.warn("Executor did not terminate in time, forcing shutdown"); + List droppedTasks = executor.shutdownNow(); + if (!droppedTasks.isEmpty()) { + log.warn("Dropped {} pending tasks during forced shutdown", droppedTasks.size()); + } + + if (!executor.awaitTermination(2, TimeUnit.SECONDS)) { + log.error("Executor did not terminate even after forced shutdown - potential thread leak"); + } + } + } catch (InterruptedException e) { + log.error("Interrupted while waiting for executor termination"); + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } +} + diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlRegistrationBuilder.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlRegistrationBuilder.java new file mode 100644 index 000000000..07766c1b7 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlRegistrationBuilder.java @@ -0,0 +1,124 @@ +package com.park.utmstack.config.saml; + +import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; +import com.park.utmstack.util.CipherUtil; +import com.park.utmstack.util.saml.PemUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; + +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +/** + * Responsible for building SAML registration objects. + * Separates credential handling and registration building logic. + */ +@Slf4j +public class SamlRegistrationBuilder { + + private final String encryptionKey; + private final SamlMetadataFetcher metadataFetcher; + + public SamlRegistrationBuilder(String encryptionKey, SamlMetadataFetcher metadataFetcher) { + this.encryptionKey = encryptionKey; + this.metadataFetcher = metadataFetcher; + } + + /** + * Builds a complete SAML registration with timeout protection and error handling. + * Returns null if any step fails. + * + * @param entity Provider configuration + * @return RelyingPartyRegistration, or null if build fails + */ + public RelyingPartyRegistration buildRegistration(IdentityProviderConfig entity) { + try { + // Step 1: Fetch metadata with timeout + RelyingPartyRegistration baseRegistration = metadataFetcher.fetchMetadataWithTimeout(entity); + if (baseRegistration == null) { + log.debug("Skipping provider '{}' - metadata fetch failed", entity.getName()); + return null; + } + + // Step 2: Load and validate credentials + PrivateKey spKey = loadAndDecryptPrivateKey(entity); + if (spKey == null) { + return null; + } + + X509Certificate spCert = loadCertificate(entity); + if (spCert == null) { + return null; + } + + // Step 3: Build final registration with credentials + return buildWithCredentials(baseRegistration, entity, spKey, spCert); + + } catch (Exception e) { + log.error("Unexpected error building SAML registration for provider '{}': {}", + entity.getName(), e.getMessage(), e); + return null; + } + } + + /** + * Loads, decrypts and validates the SP private key. + * Returns null if decryption or parsing fails. + */ + private PrivateKey loadAndDecryptPrivateKey(IdentityProviderConfig entity) { + try { + String decryptedKey = CipherUtil.decrypt(entity.getSpPrivateKeyPem(), this.encryptionKey); + return PemUtils.parsePrivateKey(decryptedKey); + } catch (Exception e) { + log.error("Failed to load/decrypt SP private key for provider '{}': {}", + entity.getName(), e.getMessage(), e); + return null; + } + } + + /** + * Loads and validates the SP certificate. + * Returns null if parsing fails. + */ + private X509Certificate loadCertificate(IdentityProviderConfig entity) { + try { + return PemUtils.parseCertificate(entity.getSpCertificatePem()); + } catch (Exception e) { + log.error("Failed to load SP certificate for provider '{}': {}", + entity.getName(), e.getMessage(), e); + return null; + } + } + + /** + * Configures the registration with SP credentials and custom settings. + */ + private RelyingPartyRegistration buildWithCredentials( + RelyingPartyRegistration baseRegistration, + IdentityProviderConfig entity, + PrivateKey spKey, + X509Certificate spCert) { + + try { + // Note: RelyingPartyRegistration from metadata is already built + // We need to configure it with our SP credentials + // For now, return the base registration built from metadata + // Additional configuration can be done via Spring Security configuration + return RelyingPartyRegistrations + .fromMetadataLocation(entity.getMetadataUrl()) + .registrationId(entity.getProviderType().name().toLowerCase()) + .assertionConsumerServiceLocation(entity.getSpAcsUrl()) + .signingX509Credentials(c -> { + c.add(Saml2X509Credential.signing(spKey, spCert)); + }) + .build(); + } catch (Exception e) { + log.error("Failed to configure SAML registration for provider '{}': {}", + entity.getName(), e.getMessage(), e); + return null; + } + } +} + diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java index 4e9c778fd..6be98303c 100644 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java @@ -3,18 +3,10 @@ import com.park.utmstack.config.Constants; import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; import com.park.utmstack.repository.idp_provider.IdentityProviderConfigRepository; -import com.park.utmstack.util.CipherUtil; -import com.park.utmstack.util.exceptions.ApiException; -import com.park.utmstack.util.saml.PemUtils; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -23,8 +15,16 @@ public class SamlRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository { private final Map registrations = new ConcurrentHashMap<>(); + private final SamlProvidersLoader providersLoader; public SamlRelyingPartyRegistrationRepository(IdentityProviderConfigRepository jpaProviderRepository) { + + String encryptionKey = getValidatedEncryptionKey(); + SamlMetadataFetcher metadataFetcher = new SamlMetadataFetcher(); + SamlRegistrationBuilder registrationBuilder = new SamlRegistrationBuilder(encryptionKey, metadataFetcher); + this.providersLoader = new SamlProvidersLoader(registrationBuilder); + + // Load providers on initialization loadProviders(jpaProviderRepository); } @@ -38,28 +38,18 @@ public void reloadProviders(IdentityProviderConfigRepository jpaProviderReposito loadProviders(jpaProviderRepository); } + /** + * Loads SAML providers using the specialized loader. + * Delegates all async loading logic to SamlProvidersLoader. + */ private void loadProviders(IdentityProviderConfigRepository jpaProviderRepository) { try { List activeProviders = jpaProviderRepository.findAllByActiveTrue(); - - if (activeProviders.isEmpty()) { - return; - } - - activeProviders.forEach(entity -> { - try { - RelyingPartyRegistration registration = buildRelyingPartyRegistration(entity); - registrations.put(entity.getProviderType().name().toLowerCase(), registration); - log.info("Loaded SAML provider: {} (type: {})", entity.getName(), entity.getProviderType()); - } catch (Exception e) { - log.error("Failed to load SAML provider: {}", entity.getName(), e); - } - }); - - log.info("Successfully loaded {} SAML provider(s)", registrations.size()); + Map loadedRegistrations = + providersLoader.loadProvidersAsync(activeProviders); + registrations.putAll(loadedRegistrations); } catch (Exception e) { - log.error("Failed to load SAML providers: {}", e.getMessage(), e); - throw new ApiException(String.format("Failed to load SAML providers: %s", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); + log.error("Error during SAML provider loading: {}", e.getMessage(), e); } } @@ -78,25 +68,4 @@ private String getValidatedEncryptionKey() { return encryptionKey; } - private RelyingPartyRegistration buildRelyingPartyRegistration(IdentityProviderConfig entity) { - try { - String encryptionKey = getValidatedEncryptionKey(); - - String decryptedKey = CipherUtil.decrypt(entity.getSpPrivateKeyPem(), encryptionKey); - PrivateKey spKey = PemUtils.parsePrivateKey(decryptedKey); - X509Certificate spCert = PemUtils.parseCertificate(entity.getSpCertificatePem()); - - return RelyingPartyRegistrations - .fromMetadataLocation(entity.getMetadataUrl()) - .registrationId(entity.getName()) - .entityId(entity.getSpEntityId()) - .assertionConsumerServiceLocation(entity.getSpAcsUrl()) - .signingX509Credentials(c -> c.add(Saml2X509Credential.signing(spKey, spCert))) - .build(); - } catch (Exception e) { - log.error("Failed to build SAML registration for provider: {}", entity.getName(), e); - throw new ApiException(String.format("Failed to build SAML registration for provider: %s", entity.getName()), HttpStatus.INTERNAL_SERVER_ERROR); - } - } - } \ No newline at end of file From c6d062923606bdaf9d8ad76f68b8eddba016601f Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 24 Feb 2026 07:12:33 -0600 Subject: [PATCH 129/240] feat(saml): enhance SAML2 login success handling with improved user not found logging and provider reloading --- ...amlRelyingPartyRegistrationRepository.java | 32 +++++++++++++------ .../saml/Saml2LoginSuccessHandler.java | 12 +++---- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java index 6be98303c..50fe2305e 100644 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java @@ -14,7 +14,7 @@ @Slf4j public class SamlRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository { - private final Map registrations = new ConcurrentHashMap<>(); + private volatile Map registrations = new ConcurrentHashMap<>(); private final SamlProvidersLoader providersLoader; public SamlRelyingPartyRegistrationRepository(IdentityProviderConfigRepository jpaProviderRepository) { @@ -24,7 +24,6 @@ public SamlRelyingPartyRegistrationRepository(IdentityProviderConfigRepository j SamlRegistrationBuilder registrationBuilder = new SamlRegistrationBuilder(encryptionKey, metadataFetcher); this.providersLoader = new SamlProvidersLoader(registrationBuilder); - // Load providers on initialization loadProviders(jpaProviderRepository); } @@ -34,22 +33,30 @@ public RelyingPartyRegistration findByRegistrationId(String registrationId) { } public void reloadProviders(IdentityProviderConfigRepository jpaProviderRepository) { - registrations.clear(); - loadProviders(jpaProviderRepository); + try { + registrations = loadActiveProviders(jpaProviderRepository); + log.info("SAML providers reloaded successfully: {} providers loaded", registrations.size()); + } catch (Exception e) { + log.error("Failed to reload SAML providers - keeping previous configuration", e); + } } /** * Loads SAML providers using the specialized loader. * Delegates all async loading logic to SamlProvidersLoader. + * App will start without providers if loading fails. */ private void loadProviders(IdentityProviderConfigRepository jpaProviderRepository) { try { - List activeProviders = jpaProviderRepository.findAllByActiveTrue(); - Map loadedRegistrations = - providersLoader.loadProvidersAsync(activeProviders); - registrations.putAll(loadedRegistrations); + registrations = loadActiveProviders(jpaProviderRepository); + if (registrations.isEmpty()) { + log.warn("No active SAML2 providers found. SAML2 authentication will not be available."); + } else { + log.info("Successfully loaded {} SAML2 provider(s) on startup", registrations.size()); + } } catch (Exception e) { - log.error("Error during SAML provider loading: {}", e.getMessage(), e); + log.error("Error during SAML provider loading - app will start without SAML2 authentication: {}", + e.getMessage(), e); } } @@ -68,4 +75,11 @@ private String getValidatedEncryptionKey() { return encryptionKey; } + private Map loadActiveProviders(IdentityProviderConfigRepository repo) { + List activeProviders = repo.findAllByActiveTrue(); + Map loaded = providersLoader.loadProvidersAsync(activeProviders); + return new ConcurrentHashMap<>(loaded); + } + + } \ No newline at end of file diff --git a/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java b/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java index 536418db3..ec3e2057f 100644 --- a/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java +++ b/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java @@ -12,7 +12,6 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.web.util.UriComponentsBuilder; @@ -22,9 +21,6 @@ import java.net.URI; import java.util.Collection; import java.util.Objects; -import java.util.Optional; - -import static com.park.utmstack.config.Constants.FRONT_BASE_URL; /** * Success handler for SAML2 login. @@ -38,7 +34,6 @@ public class Saml2LoginSuccessHandler implements AuthenticationSuccessHandler { private final TokenProvider tokenProvider; private final UserRepository userRepository; - private final Saml2LoginFailureHandler failureHandler; @Override @@ -48,14 +43,16 @@ public void onAuthenticationSuccess(HttpServletRequest request, String scheme = Objects.requireNonNullElse(request.getHeader("X-Forwarded-Proto"), request.getScheme()); String host = Objects.requireNonNullElse(request.getHeader("Host"), request.getServerName()); - String frontBaseUrl = scheme + "://" + host; Saml2AuthenticatedPrincipal samlUser = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); String username = samlUser.getName(); User user = userRepository.findOneByLogin(username) - .orElseThrow(() -> new BadCredentialsException("The provided credentials do not match any active user account.")); + .orElseThrow(() -> { + log.warn("SAML2 authentication successful for '{}' but user not found in local database", username); + return new BadCredentialsException("User not provisioned in local system"); + }); Collection authorities = Objects.requireNonNull(user.getAuthorities()) .stream() @@ -75,6 +72,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, .build() .toUri(); + log.info("SAML2 login successful for user: {}", username); response.sendRedirect(redirectUri.toString()); } } From b1b41f3ec9f817819d92411ec771326b7fe109f7 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 24 Feb 2026 07:26:41 -0600 Subject: [PATCH 130/240] feat(saml): update host retrieval in SAML2 login success handler to use X-Forwarded-Host header --- .../park/utmstack/security/saml/Saml2LoginSuccessHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java b/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java index ec3e2057f..27aabe8f5 100644 --- a/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java +++ b/backend/src/main/java/com/park/utmstack/security/saml/Saml2LoginSuccessHandler.java @@ -42,7 +42,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, Authentication authentication) throws IOException { String scheme = Objects.requireNonNullElse(request.getHeader("X-Forwarded-Proto"), request.getScheme()); - String host = Objects.requireNonNullElse(request.getHeader("Host"), request.getServerName()); + String host = Objects.requireNonNullElse(request.getHeader("X-Forwarded-Host"), request.getServerName()); String frontBaseUrl = scheme + "://" + host; Saml2AuthenticatedPrincipal samlUser = (Saml2AuthenticatedPrincipal) authentication.getPrincipal(); From f70359484dcdf03c611be83205b81834765fe68e Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 24 Feb 2026 10:44:06 -0600 Subject: [PATCH 131/240] feat(saml): refactor SAML metadata fetching to improve error handling and registration building --- .../config/SecurityConfiguration.java | 3 +- .../config/saml/SamlMetadataFetcher.java | 168 ++++-------------- .../config/saml/SamlProvidersLoader.java | 167 ----------------- .../config/saml/SamlRegistrationBuilder.java | 124 ------------- ...amlRelyingPartyRegistrationRepository.java | 35 +++- 5 files changed, 64 insertions(+), 433 deletions(-) delete mode 100644 backend/src/main/java/com/park/utmstack/config/saml/SamlProvidersLoader.java delete mode 100644 backend/src/main/java/com/park/utmstack/config/saml/SamlRegistrationBuilder.java diff --git a/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java b/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java index 9252976f4..9478be6b8 100644 --- a/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java @@ -128,8 +128,7 @@ public void configure(HttpSecurity http) throws Exception { .and() .saml2Login() .successHandler(new Saml2LoginSuccessHandler(tokenProvider, - userRepository, - saml2LoginFailureHandler())) + userRepository)) .failureHandler(new Saml2LoginFailureHandler()) .and() .apply(securityConfigurerAdapterForJwt()) diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java index 32d0eec49..9264ed181 100644 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlMetadataFetcher.java @@ -2,154 +2,56 @@ import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; -import org.springframework.stereotype.Component; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.time.Duration; import java.util.concurrent.*; -/** - * Responsible for fetching SAML metadata with timeout handling. - * Separates the concern of async metadata fetching from registration building. - */ @Slf4j public class SamlMetadataFetcher { - private static final Duration METADATA_FETCH_TIMEOUT = Duration.ofSeconds(10); - - /** - * Fetches SAML metadata with timeout protection. - * Returns null if timeout, error, or interruption occurs. - * Logs detailed error information instead of throwing exceptions. - * - * @param entity Provider configuration - * @return RelyingPartyRegistration, or null if fetch fails - */ - public RelyingPartyRegistration fetchMetadataWithTimeout(IdentityProviderConfig entity) { - ExecutorService timeoutExecutor = null; - Future future = null; + private static final Duration TIMEOUT = Duration.ofSeconds(10); + + private final ExecutorService executor = Executors.newFixedThreadPool(5, r -> { + Thread t = new Thread(r); + t.setName("saml-metadata-fetch"); + t.setDaemon(true); + return t; + }); + + public RelyingPartyRegistration fetch(IdentityProviderConfig entity, + PrivateKey spKey, + X509Certificate spCert) { + + CompletableFuture future = + CompletableFuture.supplyAsync(() -> { + try { + return RelyingPartyRegistrations + .fromMetadataLocation(entity.getMetadataUrl()) + .registrationId(entity.getName()) + .entityId(entity.getSpEntityId()) + .assertionConsumerServiceLocation(entity.getSpAcsUrl()) + .signingX509Credentials(c -> c.add(Saml2X509Credential.signing(spKey, spCert))) + .build(); + } catch (Exception e) { + throw new CompletionException(e); + } + }, executor); try { - timeoutExecutor = createMetadataFetchExecutor(entity); - - future = CompletableFuture.supplyAsync(() -> { - try { - return RelyingPartyRegistrations - .fromMetadataLocation(entity.getMetadataUrl()) - .registrationId(entity.getName()) - .build(); - } catch (Exception e) { - throw new CompletionException(e); - } - }, timeoutExecutor); - - return future.get(METADATA_FETCH_TIMEOUT.getSeconds(), TimeUnit.SECONDS); - - } catch (TimeoutException e) { - handleTimeoutException(entity, future, e); - return null; - - } catch (ExecutionException e) { - handleExecutionException(entity, e); - return null; - - } catch (InterruptedException e) { - handleInterruptedException(entity, e); - return null; - - } finally { - cleanupExecutor(entity, timeoutExecutor); - } - } + return future.get(TIMEOUT.getSeconds(), TimeUnit.SECONDS); - /** - * Creates an executor for metadata fetching with proper naming and exception handling. - */ - private ExecutorService createMetadataFetchExecutor(IdentityProviderConfig entity) { - return Executors.newSingleThreadExecutor(r -> { - Thread t = new Thread(r); - t.setName("saml-metadata-fetch-" + entity.getName()); - t.setDaemon(true); - t.setUncaughtExceptionHandler((thread, throwable) -> - log.error("Uncaught exception in SAML metadata fetch thread for {}: {}", - entity.getName(), throwable.getMessage(), throwable) - ); - return t; - }); - } - - /** - * Handles timeout exception with detailed logging. - */ - private void handleTimeoutException(IdentityProviderConfig entity, Future future, TimeoutException e) { - if (future != null) { + } catch (Exception e) { future.cancel(true); + log.error("Metadata fetch failed for provider '{}': {}", entity.getName(), e.getMessage()); + return null; } - log.error( - "SAML metadata fetch TIMEOUT: Provider='{}', Timeout={}s, MetadataUrl='{}'. " + - "This provider will not be available for SSO until it responds faster or the endpoint is fixed.", - entity.getName(), - METADATA_FETCH_TIMEOUT.getSeconds(), - entity.getMetadataUrl(), - e - ); - } - - /** - * Handles execution exception with root cause extraction. - */ - private void handleExecutionException(IdentityProviderConfig entity, ExecutionException e) { - Throwable rootCause = e.getCause() != null ? e.getCause() : e; - log.error( - "SAML metadata fetch FAILED: Provider='{}'. Root cause: {}. " + - "Error details: {}. This provider will not be available for SSO.", - entity.getName(), - rootCause.getClass().getSimpleName(), - rootCause.getMessage(), - rootCause - ); - } - - /** - * Handles interruption exception. - */ - private void handleInterruptedException(IdentityProviderConfig entity, InterruptedException e) { - Thread.currentThread().interrupt(); - log.error( - "SAML metadata fetch INTERRUPTED: Provider='{}'. " + - "Current thread was interrupted. Thread status restored. " + - "This provider will not be available for SSO.", - entity.getName(), - e - ); } +} - /** - * Safely shuts down the executor and logs any issues. - */ - private void cleanupExecutor(IdentityProviderConfig entity, ExecutorService executor) { - if (executor != null) { - try { - executor.shutdownNow(); - if (!executor.awaitTermination(2, TimeUnit.SECONDS)) { - log.warn( - "Executor for SAML provider '{}' did not terminate cleanly within 2 seconds. " + - "Potential thread leak detected.", - entity.getName() - ); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.error( - "Interrupted while waiting for executor shutdown for SAML provider '{}'. " + - "Thread status restored.", - entity.getName(), - e - ); - } - } - } -} diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlProvidersLoader.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlProvidersLoader.java deleted file mode 100644 index 58dbdac75..000000000 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlProvidersLoader.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.park.utmstack.config.saml; - -import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; - -import java.time.Duration; -import java.util.*; -import java.util.concurrent.*; - -/** - * Responsible for loading multiple SAML providers concurrently. - * Separates the concern of concurrent loading and resource management. - */ -@Slf4j -public class SamlProvidersLoader { - - private static final Duration GLOBAL_LOADING_TIMEOUT = Duration.ofSeconds(30); - private static final int MAX_CONCURRENT_LOADS = 5; - - private final SamlRegistrationBuilder registrationBuilder; - - public SamlProvidersLoader(SamlRegistrationBuilder registrationBuilder) { - this.registrationBuilder = registrationBuilder; - } - - /** - * Loads multiple providers concurrently with global timeout. - * Returns a map of loaded registrations (some may be missing if they failed). - * - * @param activeProviders List of provider configurations to load - * @return Map of provider type -> registration (only successful loads) - */ - public Map loadProvidersAsync( - List activeProviders) { - - if (activeProviders.isEmpty()) { - log.info("No active SAML providers found"); - return new ConcurrentHashMap<>(); - } - - log.info("Starting async load of {} SAML provider(s)...", activeProviders.size()); - - ExecutorService executor = null; - try { - executor = createExecutor(activeProviders.size()); - final ExecutorService finalExecutor = executor; - Map registrations = new ConcurrentHashMap<>(); - - List> futures = activeProviders.stream() - .map(entity -> loadProviderAsync(entity, registrations, finalExecutor)) - .toList(); - - waitForAllLoads(futures); - logLoadingResults(activeProviders.size(), registrations.size()); - - return registrations; - - } finally { - shutdownExecutorGracefully(executor); - } - } - - /** - * Creates a thread pool executor for concurrent provider loading. - */ - private ExecutorService createExecutor(int providerCount) { - int poolSize = Math.min(MAX_CONCURRENT_LOADS, providerCount); - return Executors.newFixedThreadPool( - poolSize, - r -> { - Thread t = new Thread(r); - t.setName("saml-provider-loader"); - t.setDaemon(true); - t.setUncaughtExceptionHandler((thread, throwable) -> - log.error("Uncaught exception in provider loading thread: {}", - throwable.getMessage(), throwable) - ); - return t; - } - ); - } - - /** - * Loads a single provider asynchronously. - */ - private CompletableFuture loadProviderAsync( - IdentityProviderConfig entity, - Map registrations, - ExecutorService executor) { - - return CompletableFuture.runAsync(() -> { - try { - RelyingPartyRegistration registration = registrationBuilder.buildRegistration(entity); - - if (registration != null) { - registrations.put(entity.getProviderType().name().toLowerCase(), registration); - log.info("Successfully loaded SAML provider: {} (type: {})", - entity.getName(), entity.getProviderType()); - } else { - log.warn("SAML provider '{}' (type: {}) skipped - unable to load registration. " + - "SSO will not be available for this provider type.", - entity.getName(), entity.getProviderType()); - } - } catch (Exception e) { - log.error("Unexpected error loading SAML provider '{}': {}", - entity.getName(), e.getMessage(), e); - } - }, executor); - } - - /** - * Waits for all provider loads to complete with global timeout. - */ - private void waitForAllLoads(List> futures) { - try { - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) - .get(GLOBAL_LOADING_TIMEOUT.getSeconds(), TimeUnit.SECONDS); - } catch (TimeoutException e) { - log.warn("Provider loading exceeded global timeout of {}s", - GLOBAL_LOADING_TIMEOUT.getSeconds()); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.error("Provider loading was interrupted"); - } catch (ExecutionException e) { - log.error("Error during provider loading: {}", e.getMessage()); - } - } - - /** - * Logs summary of loading results. - */ - private void logLoadingResults(int totalProviders, int loadedCount) { - int failedCount = totalProviders - loadedCount; - log.info("SAML provider loading completed: {} loaded, {} failed, {} total", - loadedCount, failedCount, totalProviders); - } - - /** - * Safely shuts down the executor with proper timeout handling. - */ - private void shutdownExecutorGracefully(ExecutorService executor) { - if (executor == null) { - return; - } - - try { - executor.shutdown(); - if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { - log.warn("Executor did not terminate in time, forcing shutdown"); - List droppedTasks = executor.shutdownNow(); - if (!droppedTasks.isEmpty()) { - log.warn("Dropped {} pending tasks during forced shutdown", droppedTasks.size()); - } - - if (!executor.awaitTermination(2, TimeUnit.SECONDS)) { - log.error("Executor did not terminate even after forced shutdown - potential thread leak"); - } - } - } catch (InterruptedException e) { - log.error("Interrupted while waiting for executor termination"); - executor.shutdownNow(); - Thread.currentThread().interrupt(); - } - } -} - diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlRegistrationBuilder.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlRegistrationBuilder.java deleted file mode 100644 index 07766c1b7..000000000 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlRegistrationBuilder.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.park.utmstack.config.saml; - -import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; -import com.park.utmstack.util.CipherUtil; -import com.park.utmstack.util.saml.PemUtils; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; - -import java.security.PrivateKey; -import java.security.cert.X509Certificate; - -/** - * Responsible for building SAML registration objects. - * Separates credential handling and registration building logic. - */ -@Slf4j -public class SamlRegistrationBuilder { - - private final String encryptionKey; - private final SamlMetadataFetcher metadataFetcher; - - public SamlRegistrationBuilder(String encryptionKey, SamlMetadataFetcher metadataFetcher) { - this.encryptionKey = encryptionKey; - this.metadataFetcher = metadataFetcher; - } - - /** - * Builds a complete SAML registration with timeout protection and error handling. - * Returns null if any step fails. - * - * @param entity Provider configuration - * @return RelyingPartyRegistration, or null if build fails - */ - public RelyingPartyRegistration buildRegistration(IdentityProviderConfig entity) { - try { - // Step 1: Fetch metadata with timeout - RelyingPartyRegistration baseRegistration = metadataFetcher.fetchMetadataWithTimeout(entity); - if (baseRegistration == null) { - log.debug("Skipping provider '{}' - metadata fetch failed", entity.getName()); - return null; - } - - // Step 2: Load and validate credentials - PrivateKey spKey = loadAndDecryptPrivateKey(entity); - if (spKey == null) { - return null; - } - - X509Certificate spCert = loadCertificate(entity); - if (spCert == null) { - return null; - } - - // Step 3: Build final registration with credentials - return buildWithCredentials(baseRegistration, entity, spKey, spCert); - - } catch (Exception e) { - log.error("Unexpected error building SAML registration for provider '{}': {}", - entity.getName(), e.getMessage(), e); - return null; - } - } - - /** - * Loads, decrypts and validates the SP private key. - * Returns null if decryption or parsing fails. - */ - private PrivateKey loadAndDecryptPrivateKey(IdentityProviderConfig entity) { - try { - String decryptedKey = CipherUtil.decrypt(entity.getSpPrivateKeyPem(), this.encryptionKey); - return PemUtils.parsePrivateKey(decryptedKey); - } catch (Exception e) { - log.error("Failed to load/decrypt SP private key for provider '{}': {}", - entity.getName(), e.getMessage(), e); - return null; - } - } - - /** - * Loads and validates the SP certificate. - * Returns null if parsing fails. - */ - private X509Certificate loadCertificate(IdentityProviderConfig entity) { - try { - return PemUtils.parseCertificate(entity.getSpCertificatePem()); - } catch (Exception e) { - log.error("Failed to load SP certificate for provider '{}': {}", - entity.getName(), e.getMessage(), e); - return null; - } - } - - /** - * Configures the registration with SP credentials and custom settings. - */ - private RelyingPartyRegistration buildWithCredentials( - RelyingPartyRegistration baseRegistration, - IdentityProviderConfig entity, - PrivateKey spKey, - X509Certificate spCert) { - - try { - // Note: RelyingPartyRegistration from metadata is already built - // We need to configure it with our SP credentials - // For now, return the base registration built from metadata - // Additional configuration can be done via Spring Security configuration - return RelyingPartyRegistrations - .fromMetadataLocation(entity.getMetadataUrl()) - .registrationId(entity.getProviderType().name().toLowerCase()) - .assertionConsumerServiceLocation(entity.getSpAcsUrl()) - .signingX509Credentials(c -> { - c.add(Saml2X509Credential.signing(spKey, spCert)); - }) - .build(); - } catch (Exception e) { - log.error("Failed to configure SAML registration for provider '{}': {}", - entity.getName(), e.getMessage(), e); - return null; - } - } -} - diff --git a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java index 50fe2305e..d466998af 100644 --- a/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java +++ b/backend/src/main/java/com/park/utmstack/config/saml/SamlRelyingPartyRegistrationRepository.java @@ -3,10 +3,14 @@ import com.park.utmstack.config.Constants; import com.park.utmstack.domain.idp_provider.IdentityProviderConfig; import com.park.utmstack.repository.idp_provider.IdentityProviderConfigRepository; +import com.park.utmstack.util.CipherUtil; +import com.park.utmstack.util.saml.PemUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -15,14 +19,13 @@ public class SamlRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository { private volatile Map registrations = new ConcurrentHashMap<>(); - private final SamlProvidersLoader providersLoader; + private final SamlMetadataFetcher fetcher; + private final String encryptionKey; public SamlRelyingPartyRegistrationRepository(IdentityProviderConfigRepository jpaProviderRepository) { - String encryptionKey = getValidatedEncryptionKey(); - SamlMetadataFetcher metadataFetcher = new SamlMetadataFetcher(); - SamlRegistrationBuilder registrationBuilder = new SamlRegistrationBuilder(encryptionKey, metadataFetcher); - this.providersLoader = new SamlProvidersLoader(registrationBuilder); + encryptionKey = getValidatedEncryptionKey(); + fetcher = new SamlMetadataFetcher(); loadProviders(jpaProviderRepository); } @@ -76,9 +79,27 @@ private String getValidatedEncryptionKey() { } private Map loadActiveProviders(IdentityProviderConfigRepository repo) { + + Map map = new ConcurrentHashMap<>(); + List activeProviders = repo.findAllByActiveTrue(); - Map loaded = providersLoader.loadProvidersAsync(activeProviders); - return new ConcurrentHashMap<>(loaded); + + activeProviders.forEach(entity -> { + + PrivateKey spKey = PemUtils.parsePrivateKey(CipherUtil.decrypt( + entity.getSpPrivateKeyPem(), + encryptionKey)); + + X509Certificate spCert = PemUtils.parseCertificate(entity.getSpCertificatePem()); + + RelyingPartyRegistration reg = fetcher.fetch(entity, spKey, spCert); + + if (reg != null) { + map.put(entity.getProviderType().name().toLowerCase(), reg); + } + }); + + return map; } From 007d5b5d12227970cb70adc3bd38e35b6b163b35 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 24 Feb 2026 11:22:46 -0600 Subject: [PATCH 132/240] feat(platforms): enhance platform creation with additional Linux ARM64 support and update Windows service paths Signed-off-by: Manuel Abascal --- .../guide-syslog/guide-syslog.component.ts | 9 -- .../app/app-module/guides/shared/constant.ts | 110 ++++++++++-------- 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts b/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts index 0f855f2d7..6f032090a 100644 --- a/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts +++ b/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts @@ -16,15 +16,6 @@ export class GuideSyslogComponent implements OnInit { @Input() moduleEnum: UtmModulesEnum; @Input() dataType: string; module = UtmModulesEnum; - moduleImages: SyslogModuleImages[] = [ - {module: UtmModulesEnum.FORTIGATE, img: 'fortigate.png'}, - {module: UtmModulesEnum.UFW, img: 'ufw.png'}, - {module: UtmModulesEnum.MIKROTIK, img: 'mikrotik.png'}, - {module: UtmModulesEnum.PALO_ALTO, img: 'paloalto.png'}, - {module: UtmModulesEnum.SONIC_WALL, img: 'sonicwall.png'}, - {module: UtmModulesEnum.DECEPTIVE_BYTES, img: 'deceptivebytes.png'}, - {module: UtmModulesEnum.SOPHOS_XG, img: 'sophosxg.png'} - ]; syslogPorts: SyslogModulePorts[] = [ {module: UtmModulesEnum.FORTIGATE, port: '7005 TCP'}, diff --git a/frontend/src/app/app-module/guides/shared/constant.ts b/frontend/src/app/app-module/guides/shared/constant.ts index 468dd47fc..96c839d23 100644 --- a/frontend/src/app/app-module/guides/shared/constant.ts +++ b/frontend/src/app/app-module/guides/shared/constant.ts @@ -22,54 +22,69 @@ function createPlatform( path?: string, restart?: string, extraCommands?: string[]): Platform { - return { id, name, command, shell, path, restart, extraCommands }; + return {id, name, command, shell, path, restart, extraCommands}; } export const createPlatforms = ( - windowsCommandAMD64: string, - windowsCommandARM64: string, - linuxCommand: string, - windowsPath?: string, - windowsRestart?: string, - linuxPath?: string, - linuxRestart?: string): Platform[] => [ - createPlatform( - 1, - 'WINDOWS (AMD64)', - windowsCommandAMD64, - WINDOWS_SHELL, - windowsPath, - windowsRestart,[ - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service.exe" ' + - '-ArgumentList \'load-tls-certs\', \'[YOUR_CERT_PATH]\', \'[YOUR_KEY_PATH]\' ' + - '-NoNewWindow -Wait' - ] - ), - createPlatform( - 2, - 'WINDOWS (ARM64)', - windowsCommandARM64, - WINDOWS_SHELL, - windowsPath, - windowsRestart, - [ - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_arm64.exe" ' + - '-ArgumentList \'load-tls-certs\', \'[YOUR_CERT_PATH]\', \'[YOUR_KEY_PATH]\' ' + - '-NoNewWindow -Wait' - ] - ), - createPlatform( - 3, - 'LINUX', - linuxCommand, - LINUX_SHELL, - linuxPath, - linuxRestart, - [ - `sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service load-tls-certs [YOUR_CERT_PATH] [YOUR_KEY_PATH]"` - ] - ) -]; + windowsCommandAMD64: string, + windowsCommandARM64: string, + linuxCommandAMD64: string, + linuxCommandARM64: string, + windowsPath?: string, + windowsRestart?: string, + linuxPath?: string, + linuxRestart?: string): Platform[] => [ + + createPlatform( + 1, + 'WINDOWS (AMD64)', + windowsCommandAMD64, + WINDOWS_SHELL, + windowsPath, + windowsRestart, [ + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_amd64.exe" ' + + '-ArgumentList \'load-tls-certs\', \'[YOUR_CERT_PATH]\', \'[YOUR_KEY_PATH]\' ' + + '-NoNewWindow -Wait' + ] + ), + + createPlatform( + 2, + 'WINDOWS (ARM64)', + windowsCommandARM64, + WINDOWS_SHELL, + windowsPath, + windowsRestart, + [ + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_arm64.exe" ' + + '-ArgumentList \'load-tls-certs\', \'[YOUR_CERT_PATH]\', \'[YOUR_KEY_PATH]\' ' + + '-NoNewWindow -Wait' + ] + ), + createPlatform( + 3, + 'LINUX (AMD64)', + linuxCommandAMD64, + LINUX_SHELL, + linuxPath, + linuxRestart, + [ + `sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service load-tls-certs [YOUR_CERT_PATH] [YOUR_KEY_PATH]"` + ] + ), + createPlatform( + 3, + 'LINUX (ARM64)', + linuxCommandARM64, + LINUX_SHELL, + linuxPath, + linuxRestart, + [ + `sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service load-tls-certs [YOUR_CERT_PATH] [YOUR_KEY_PATH]"` + ] + ) + ] +; export const createFileBeatsPlatforms = ( windowsCommand: string, @@ -97,9 +112,10 @@ export const createFileBeatsPlatforms = ( ]; export const PLATFORMS = createPlatforms( - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service.exe" -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\', \'TLS\' -NoNewWindow -Wait\n', + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_amd64.exe " -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\', \'TLS\' -NoNewWindow -Wait\n', 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_arm64.exe" -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\, \'TLS\' -NoNewWindow -Wait\n', - 'sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service ACTION AGENT_NAME PROTOCOL TLS"' + 'sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service_amd64 ACTION AGENT_NAME PROTOCOL TLS"', + 'sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service_arm64 ACTION AGENT_NAME PROTOCOL TLS"' ); export const FILEBEAT_PLATFORMS = createFileBeatsPlatforms( From 82f7acef3e8a7b618b6ab528fcf503f5cf97e144 Mon Sep 17 00:00:00 2001 From: Osmany Montero Date: Tue, 24 Feb 2026 21:01:17 +0000 Subject: [PATCH 133/240] fix(config): detect filter and rule deletions by tracking active row counts hasChanges only checked MAX(timestamp) increases, missing deletions where the timestamp didn't advance. Now also compares COUNT of active rows so deactivations and hard deletes trigger config file regeneration. Co-Authored-By: Claude Opus 4.6 --- plugins/config/main.go | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/plugins/config/main.go b/plugins/config/main.go index 3db45fca5..5d596d59f 100644 --- a/plugins/config/main.go +++ b/plugins/config/main.go @@ -75,9 +75,13 @@ type ExpressionBackend struct { type ConfigState struct { AssetsLastUpdate time.Time + AssetsCount int RulesLastUpdate time.Time + RulesCount int FiltersLastUpdate time.Time + FiltersCount int PatternsLastUpdate time.Time + PatternsCount int } func (b *ExpressionBackend) ToExpression() Expression { @@ -431,26 +435,35 @@ func hasChanges(db *sql.DB, state *ConfigState) (bool, ConfigState, error) { changed := false queries := []struct { - query string - target *time.Time - old time.Time + timestampQuery string + countQuery string + targetTime *time.Time + targetCount *int + oldTime time.Time + oldCount int }{ - {"SELECT MAX(last_update) FROM utm_tenant_config", &newState.AssetsLastUpdate, state.AssetsLastUpdate}, - {"SELECT MAX(rule_last_update) FROM utm_correlation_rules", &newState.RulesLastUpdate, state.RulesLastUpdate}, - {"SELECT MAX(updated_at) FROM utm_logstash_filter", &newState.FiltersLastUpdate, state.FiltersLastUpdate}, - {"SELECT MAX(last_update) FROM utm_regex_pattern", &newState.PatternsLastUpdate, state.PatternsLastUpdate}, + {"SELECT MAX(last_update) FROM utm_tenant_config", "SELECT COUNT(*) FROM utm_tenant_config", &newState.AssetsLastUpdate, &newState.AssetsCount, state.AssetsLastUpdate, state.AssetsCount}, + {"SELECT MAX(rule_last_update) FROM utm_correlation_rules", "SELECT COUNT(*) FROM utm_correlation_rules WHERE rule_active = true", &newState.RulesLastUpdate, &newState.RulesCount, state.RulesLastUpdate, state.RulesCount}, + {"SELECT MAX(updated_at) FROM utm_logstash_filter", "SELECT COUNT(*) FROM utm_logstash_filter WHERE is_active = true", &newState.FiltersLastUpdate, &newState.FiltersCount, state.FiltersLastUpdate, state.FiltersCount}, + {"SELECT MAX(last_update) FROM utm_regex_pattern", "SELECT COUNT(*) FROM utm_regex_pattern", &newState.PatternsLastUpdate, &newState.PatternsCount, state.PatternsLastUpdate, state.PatternsCount}, } for _, q := range queries { var lastUpdate sql.NullTime - err := db.QueryRow(q.query).Scan(&lastUpdate) + err := db.QueryRow(q.timestampQuery).Scan(&lastUpdate) if err != nil { return false, newState, err } if lastUpdate.Valid { - *q.target = lastUpdate.Time + *q.targetTime = lastUpdate.Time } - if (*q.target).After(q.old) { + + err = db.QueryRow(q.countQuery).Scan(q.targetCount) + if err != nil { + return false, newState, err + } + + if (*q.targetTime).After(q.oldTime) || *q.targetCount != q.oldCount { changed = true } } From d570851c52c86b4e2fc83094acb4f86bbe09f300 Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Tue, 24 Feb 2026 16:06:55 -0500 Subject: [PATCH 134/240] Update go-sdk dependency across multiple plugins --- plugins/alerts/go.mod | 2 +- plugins/alerts/go.sum | 4 ++-- plugins/aws/go.mod | 2 +- plugins/aws/go.sum | 4 ++-- plugins/azure/go.mod | 2 +- plugins/azure/go.sum | 4 ++-- plugins/bitdefender/go.mod | 2 +- plugins/bitdefender/go.sum | 4 ++-- plugins/config/go.mod | 2 +- plugins/config/go.sum | 4 ++-- plugins/crowdstrike/go.mod | 2 +- plugins/crowdstrike/go.sum | 4 ++-- plugins/events/go.mod | 2 +- plugins/events/go.sum | 4 ++-- plugins/feeds/go.mod | 2 +- plugins/feeds/go.sum | 4 ++-- plugins/gcp/go.mod | 2 +- plugins/gcp/go.sum | 4 ++-- plugins/geolocation/go.mod | 2 +- plugins/geolocation/go.sum | 25 +++++++++++++++++++++++++ plugins/inputs/go.mod | 2 +- plugins/inputs/go.sum | 4 ++-- plugins/modules-config/go.mod | 2 +- plugins/modules-config/go.sum | 4 ++-- plugins/o365/go.mod | 2 +- plugins/o365/go.sum | 4 ++-- plugins/sophos/go.mod | 2 +- plugins/sophos/go.sum | 4 ++-- plugins/stats/go.mod | 2 +- plugins/stats/go.sum | 4 ++-- utmstack-collector/go.mod | 2 +- utmstack-collector/go.sum | 4 ++-- 32 files changed, 71 insertions(+), 46 deletions(-) diff --git a/plugins/alerts/go.mod b/plugins/alerts/go.mod index ec1fd100d..37e0a8559 100644 --- a/plugins/alerts/go.mod +++ b/plugins/alerts/go.mod @@ -3,7 +3,7 @@ module github.com/utmstack/UTMStack/plugins/alerts go 1.25.5 require ( - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 github.com/tidwall/gjson v1.18.0 google.golang.org/protobuf v1.36.11 ) diff --git a/plugins/alerts/go.sum b/plugins/alerts/go.sum index 1abc5a7c4..fb814cb43 100644 --- a/plugins/alerts/go.sum +++ b/plugins/alerts/go.sum @@ -88,8 +88,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/aws/go.mod b/plugins/aws/go.mod index 3e66b6e26..f76bb3dae 100644 --- a/plugins/aws/go.mod +++ b/plugins/aws/go.mod @@ -7,7 +7,7 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.32.8 github.com/aws/aws-sdk-go-v2/credentials v1.19.8 github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 ) require ( diff --git a/plugins/aws/go.sum b/plugins/aws/go.sum index f967d7975..24f0c5b67 100644 --- a/plugins/aws/go.sum +++ b/plugins/aws/go.sum @@ -122,8 +122,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/azure/go.mod b/plugins/azure/go.mod index 55ed03781..7af3ef831 100644 --- a/plugins/azure/go.mod +++ b/plugins/azure/go.mod @@ -7,7 +7,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/v2 v2.0.1 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) diff --git a/plugins/azure/go.sum b/plugins/azure/go.sum index 88b1689df..8041018ed 100644 --- a/plugins/azure/go.sum +++ b/plugins/azure/go.sum @@ -122,8 +122,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/bitdefender/go.mod b/plugins/bitdefender/go.mod index d251061a0..f23ff74f3 100644 --- a/plugins/bitdefender/go.mod +++ b/plugins/bitdefender/go.mod @@ -5,7 +5,7 @@ go 1.25.5 require ( github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) diff --git a/plugins/bitdefender/go.sum b/plugins/bitdefender/go.sum index 2eb91827c..f5cf22703 100644 --- a/plugins/bitdefender/go.sum +++ b/plugins/bitdefender/go.sum @@ -92,8 +92,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/config/go.mod b/plugins/config/go.mod index 959755c23..b94d8e1dd 100644 --- a/plugins/config/go.mod +++ b/plugins/config/go.mod @@ -4,7 +4,7 @@ go 1.25.5 require ( github.com/lib/pq v1.11.2 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 gopkg.in/yaml.v3 v3.0.1 sigs.k8s.io/yaml v1.6.0 ) diff --git a/plugins/config/go.sum b/plugins/config/go.sum index 6f922c27f..106bc7804 100644 --- a/plugins/config/go.sum +++ b/plugins/config/go.sum @@ -90,8 +90,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/crowdstrike/go.mod b/plugins/crowdstrike/go.mod index 3dbaf59a6..81a249a21 100644 --- a/plugins/crowdstrike/go.mod +++ b/plugins/crowdstrike/go.mod @@ -5,7 +5,7 @@ go 1.25.5 require ( github.com/crowdstrike/gofalcon v0.19.0 github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) diff --git a/plugins/crowdstrike/go.sum b/plugins/crowdstrike/go.sum index 494a0d183..0ef3a0f64 100644 --- a/plugins/crowdstrike/go.sum +++ b/plugins/crowdstrike/go.sum @@ -149,8 +149,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/events/go.mod b/plugins/events/go.mod index 01104171c..b5edeb8c1 100644 --- a/plugins/events/go.mod +++ b/plugins/events/go.mod @@ -3,7 +3,7 @@ module github.com/utmstack/UTMStack/plugins/events go 1.25.5 require ( - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 github.com/tidwall/gjson v1.18.0 ) diff --git a/plugins/events/go.sum b/plugins/events/go.sum index 1abc5a7c4..fb814cb43 100644 --- a/plugins/events/go.sum +++ b/plugins/events/go.sum @@ -88,8 +88,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/feeds/go.mod b/plugins/feeds/go.mod index f8fa84d58..96c2044a9 100644 --- a/plugins/feeds/go.mod +++ b/plugins/feeds/go.mod @@ -5,7 +5,7 @@ go 1.25.5 require ( github.com/AtlasInsideCorp/AtlasInsideAES v1.0.0 github.com/opensearch-project/opensearch-go/v2 v2.3.0 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 golang.org/x/sync v0.19.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/plugins/feeds/go.sum b/plugins/feeds/go.sum index f8fdd174d..a8c1262b1 100644 --- a/plugins/feeds/go.sum +++ b/plugins/feeds/go.sum @@ -110,8 +110,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/gcp/go.mod b/plugins/gcp/go.mod index ca74803b1..f95db4dc9 100644 --- a/plugins/gcp/go.mod +++ b/plugins/gcp/go.mod @@ -5,7 +5,7 @@ go 1.25.5 require ( cloud.google.com/go/pubsub v1.50.1 github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 google.golang.org/api v0.267.0 google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 diff --git a/plugins/gcp/go.sum b/plugins/gcp/go.sum index 71153b223..02a528ce1 100644 --- a/plugins/gcp/go.sum +++ b/plugins/gcp/go.sum @@ -158,8 +158,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/geolocation/go.mod b/plugins/geolocation/go.mod index 2b49180db..8cc022fe6 100644 --- a/plugins/geolocation/go.mod +++ b/plugins/geolocation/go.mod @@ -3,7 +3,7 @@ module github.com/utmstack/UTMStack/plugins/geolocation go 1.25.5 require ( - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 ) diff --git a/plugins/geolocation/go.sum b/plugins/geolocation/go.sum index a37652182..ad1333a84 100644 --- a/plugins/geolocation/go.sum +++ b/plugins/geolocation/go.sum @@ -90,6 +90,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -111,14 +113,19 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -127,25 +134,43 @@ 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/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y= +golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= 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/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= +google.golang.org/genproto/googleapis/api v0.0.0-20260223185530-2f722ef697dc h1:ULD+ToGXUIU6Pkzr1ARxdyvwfHbelw+agoFDRbLg4TU= +google.golang.org/genproto/googleapis/api v0.0.0-20260223185530-2f722ef697dc/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260223185530-2f722ef697dc h1:51Wupg8spF+5FC6D+iMKbOddFjMckETnNnEiZ+HX37s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260223185530-2f722ef697dc/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= 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= diff --git a/plugins/inputs/go.mod b/plugins/inputs/go.mod index 906942c47..f9f500cce 100644 --- a/plugins/inputs/go.mod +++ b/plugins/inputs/go.mod @@ -5,7 +5,7 @@ go 1.25.5 require ( github.com/gin-gonic/gin v1.11.0 github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) diff --git a/plugins/inputs/go.sum b/plugins/inputs/go.sum index 7402b79de..72d534d36 100644 --- a/plugins/inputs/go.sum +++ b/plugins/inputs/go.sum @@ -90,8 +90,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/modules-config/go.mod b/plugins/modules-config/go.mod index 5f06af9bb..c733f16ba 100644 --- a/plugins/modules-config/go.mod +++ b/plugins/modules-config/go.mod @@ -12,7 +12,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 github.com/crowdstrike/gofalcon v0.19.0 github.com/gin-gonic/gin v1.11.0 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 google.golang.org/api v0.267.0 google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 diff --git a/plugins/modules-config/go.sum b/plugins/modules-config/go.sum index 48bf6b99f..529be11de 100644 --- a/plugins/modules-config/go.sum +++ b/plugins/modules-config/go.sum @@ -280,8 +280,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/o365/go.mod b/plugins/o365/go.mod index 9fc336715..53c619ae0 100644 --- a/plugins/o365/go.mod +++ b/plugins/o365/go.mod @@ -4,7 +4,7 @@ go 1.25.5 require ( github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) diff --git a/plugins/o365/go.sum b/plugins/o365/go.sum index 7402b79de..72d534d36 100644 --- a/plugins/o365/go.sum +++ b/plugins/o365/go.sum @@ -90,8 +90,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/sophos/go.mod b/plugins/sophos/go.mod index 6bd956d58..4dfeb6264 100644 --- a/plugins/sophos/go.mod +++ b/plugins/sophos/go.mod @@ -4,7 +4,7 @@ go 1.25.5 require ( github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) diff --git a/plugins/sophos/go.sum b/plugins/sophos/go.sum index 7402b79de..72d534d36 100644 --- a/plugins/sophos/go.sum +++ b/plugins/sophos/go.sum @@ -90,8 +90,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/plugins/stats/go.mod b/plugins/stats/go.mod index 8f5733dc0..18ca43b23 100644 --- a/plugins/stats/go.mod +++ b/plugins/stats/go.mod @@ -4,7 +4,7 @@ go 1.25.5 require ( github.com/google/uuid v1.6.0 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 google.golang.org/protobuf v1.36.11 ) diff --git a/plugins/stats/go.sum b/plugins/stats/go.sum index 1abc5a7c4..fb814cb43 100644 --- a/plugins/stats/go.sum +++ b/plugins/stats/go.sum @@ -88,8 +88,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= diff --git a/utmstack-collector/go.mod b/utmstack-collector/go.mod index 166d9165b..1260dd667 100644 --- a/utmstack-collector/go.mod +++ b/utmstack-collector/go.mod @@ -9,7 +9,7 @@ require ( github.com/glebarez/sqlite v1.11.0 github.com/google/uuid v1.6.0 github.com/kardianos/service v1.2.4 - github.com/threatwinds/go-sdk v1.1.14 + github.com/threatwinds/go-sdk v1.1.15 github.com/threatwinds/logger v1.2.3 google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 diff --git a/utmstack-collector/go.sum b/utmstack-collector/go.sum index ba290d189..51823e3b5 100644 --- a/utmstack-collector/go.sum +++ b/utmstack-collector/go.sum @@ -161,8 +161,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= +github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= +github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/threatwinds/logger v1.2.3 h1:V2SVAXzbq+/huCvIWOfqzMTH+WBHJxankyBgVG2hy1Y= github.com/threatwinds/logger v1.2.3/go.mod h1:N+bJKvF4FQNJZLfQpVYWpr6D8iEAFnAQfHYqH5iR1TI= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= From 7e4a0c0d9b471a769391392205f50d1b72b55541 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 24 Feb 2026 15:10:17 -0600 Subject: [PATCH 135/240] feat(logstash): integrate Monaco Editor for YAML filter definition and enhance form styling Signed-off-by: Manuel Abascal --- .../logstash-filter-create.component.html | 96 +++++++------- .../logstash-filter-create.component.scss | 117 ++++++++++++++++-- .../logstash-filter-create.component.ts | 54 +++++++- frontend/src/app/logstash/logstash.module.ts | 27 ++-- 4 files changed, 223 insertions(+), 71 deletions(-) diff --git a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.html b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.html index acc79bedf..9b27fc43a 100644 --- a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.html +++ b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.html @@ -1,4 +1,4 @@ -
+
Read the logs filter documentation at
-
+
-
- - -
-
-
- - - -
- Data type is required. +
+ + +
+
+
+ + + +
+ Data type is required. +
-
-
- - -
-
- - -
+
+ +
+ + +
+
+ +
+ + +
diff --git a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss index 98651c800..406f9663b 100644 --- a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss +++ b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss @@ -1,21 +1,112 @@ -:host ::ng-deep json-editor, -:host ::ng-deep json-editor .jsoneditor, -:host ::ng-deep json-editor > div, -:host ::ng-deep json-editor jsoneditor-outer { - height: 90% !important; - border: none !important; +:host { + display: block; + height: auto; } +.template-editor { + height: 800px!important; +} + +.logstash-filter-wrapper { + display: flex; + flex-direction: column; + width: 100%; + height: auto; + padding: 0.5rem; + gap: 1rem; +} + +.logstash-filter-form { + display: flex; + flex-direction: column; + flex: 1; + gap: 1rem; +} + +.yaml-editor-group { + display: flex; + flex-direction: column; + flex: 1; + min-height: 600px; + margin: 0 !important; + + label { + margin-bottom: 0.5rem; + font-weight: 500; + } +} + +.yaml-editor-container { + border: 1px solid #d9d9d9; + border-radius: 4px; + overflow: hidden; + height: 600px; + width: 100%; + flex: 1; + + ::ng-deep { + .monaco-editor { + width: 100% !important; + height: 100% !important; + } + + .monaco-editor-background { + background-color: #f5f5f5 !important; + } + + .overflow-guard { + width: 100% !important; + height: 100% !important; + } + + .monaco-scrollable-element { + width: 100% !important; + height: 100% !important; + } -::ng-deep { - .jsoneditor { - .jsoneditor-menu { - background-color: #232f3e !important; - border-bottom: 0 solid transparent !important; + .monaco-editor.readonly { + background-color: #f0f0f0 !important; + opacity: 0.8 !important; + } + + .monaco-editor.readonly .monaco-editor-background { + background-color: #f0f0f0 !important; + } + + // Syntax highlighting colors for YAML + .mtk5 { + color: #0277bd !important; // Keys - Blue + font-weight: 600 !important; + } + + .mtk1 { + color: #ff6b6b !important; // Values - Red } } +} + +.form-actions { + flex-shrink: 0; + margin-top: 1rem !important; +} - .pico-modal-header { - background-color: #232f3e !important; +@media screen and (max-height: 1000px) { + .yaml-editor-group { + min-height: 500px; + } + + .yaml-editor-container { + height: 500px; + } +} + +@media screen and (min-height: 1200px) { + .yaml-editor-group { + min-height: 700px; + } + + .yaml-editor-container { + height: 700px; } } + diff --git a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.ts b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.ts index 31b54d600..0d2529494 100644 --- a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.ts +++ b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.ts @@ -1,4 +1,4 @@ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output, OnChanges, SimpleChanges, ViewChild, AfterViewInit} from '@angular/core'; import {Observable} from 'rxjs'; import {DataType} from '../../../rule-management/models/rule.model'; import {DataTypeService} from '../../../rule-management/services/data-type.service'; @@ -12,13 +12,15 @@ import {UtmPipeline} from '../../shared/types/logstash-stats.type'; templateUrl: './logstash-filter-create.component.html', styleUrls: ['./logstash-filter-create.component.scss'] }) -export class LogstashFilterCreateComponent implements OnInit { +export class LogstashFilterCreateComponent implements OnInit, OnChanges { @Output() filterEditClose = new EventEmitter(); @Output() close = new EventEmitter(); @Input() filter: LogstashFilterType; @Input() pipeline: UtmPipeline; @Input() dataType: DataType; @Input() disable = false; + @ViewChild('editorContainer') editorContainer: any; + types$: Observable; daTypeRequest: {page: number, size: number} = { page: -1, @@ -29,6 +31,24 @@ export class LogstashFilterCreateComponent implements OnInit { show = true; error = false; loadingDataTypes = false; + private editorInstance: any; + + editorOptions: any = { + theme: 'vs-light', + language: 'yaml', + automaticLayout: true, + fontSize: 13, + fontFamily: 'Courier New, Monaco, Menlo, monospace', + lineNumbers: 'on', + wordWrap: 'on', + minimap: { enabled: false }, + scrollBeyondLastLine: false, + formatOnPaste: true, + formatOnType: true, + tabSize: 2, + insertSpaces: true, + readOnly: false + }; constructor(private logstashFilterService: LogstashService, private utmToastService: UtmToastService, @@ -42,7 +62,33 @@ export class LogstashFilterCreateComponent implements OnInit { this.types$ = this.dataTypeService.type$; this.loadDataTypes(); + this.updateEditorOptions(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes && changes['disable']) { + this.updateEditorOptions(); + } + } + + private forceEditorLayout(): void { + try { + if (this.editorContainer) { + const editorInstance = (this.editorContainer as any).editor; + if (editorInstance && editorInstance.layout) { + editorInstance.layout(); + } + } + } catch (e) { + console.warn('Error al forzar layout del editor Monaco:', e); + } + } + private updateEditorOptions(): void { + this.editorOptions = { + ...this.editorOptions, + readOnly: this.disable + }; } @@ -94,4 +140,8 @@ export class LogstashFilterCreateComponent implements OnInit { trackByFn(type: DataType) { return type.id; } + + onEditorChange(value: string): void { + this.filter.logstashFilter = value; + } } diff --git a/frontend/src/app/logstash/logstash.module.ts b/frontend/src/app/logstash/logstash.module.ts index 23ba9bf8e..6b74f42e3 100644 --- a/frontend/src/app/logstash/logstash.module.ts +++ b/frontend/src/app/logstash/logstash.module.ts @@ -2,29 +2,30 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; +import {NgSelectModule} from '@ng-select/ng-select'; import {InlineSVGModule} from 'ng-inline-svg'; +import {MonacoEditorModule} from 'ngx-monaco-editor'; import {AlertManagementSharedModule} from '../data-management/alert-management/shared/alert-management-shared.module'; import {UtmSharedModule} from '../shared/utm-shared.module'; import {LogstashFilterCreateComponent} from './logstash-filters/logstash-filter-create/logstash-filter-create.component'; import {LogstashFiltersComponent} from './logstash-filters/logstash-filters.component'; import {LogstashPipelinesComponent} from './logstash-pipelines/logstash-pipelines.component'; import {LogstashRoutingModule} from './logstash-routing.module'; -import {NgSelectModule} from '@ng-select/ng-select'; -import {DataTypeService} from '../rule-management/services/data-type.service'; @NgModule({ declarations: [LogstashFiltersComponent, LogstashFilterCreateComponent, LogstashPipelinesComponent], - imports: [ - CommonModule, - UtmSharedModule, - FormsModule, - NgbModule, - LogstashRoutingModule, - InlineSVGModule, - AlertManagementSharedModule, - NgSelectModule, - ReactiveFormsModule - ], + imports: [ + CommonModule, + UtmSharedModule, + FormsModule, + NgbModule, + LogstashRoutingModule, + InlineSVGModule, + AlertManagementSharedModule, + NgSelectModule, + ReactiveFormsModule, + MonacoEditorModule + ], entryComponents: [LogstashFilterCreateComponent] }) export class LogstashModule { From 251899500b02ed7c0c0b7d1ac43fb00160dcd898 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 25 Feb 2026 08:01:54 -0600 Subject: [PATCH 136/240] feat: update filter card interaction to improve usability Signed-off-by: Manuel Abascal --- .../logstash/logstash-filters/logstash-filters.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/logstash/logstash-filters/logstash-filters.component.html b/frontend/src/app/logstash/logstash-filters/logstash-filters.component.html index f2e37d578..aef7de0ea 100644 --- a/frontend/src/app/logstash/logstash-filters/logstash-filters.component.html +++ b/frontend/src/app/logstash/logstash-filters/logstash-filters.component.html @@ -20,9 +20,9 @@
Pipeline filters
-
+
-
+
Date: Wed, 25 Feb 2026 08:51:05 -0600 Subject: [PATCH 137/240] feat(rule-view): integrate Monaco Editor for YAML editing and enhance styling Signed-off-by: Manuel Abascal --- .../logstash-filter-create.component.scss | 19 +++-- .../see-rule/rule-view.component.html | 14 +++- .../see-rule/rule-view.component.scss | 81 +++++++++++++------ .../see-rule/rule-view.component.ts | 31 ++++--- .../rule-management/rule-management.module.ts | 4 +- 5 files changed, 105 insertions(+), 44 deletions(-) diff --git a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss index 406f9663b..070803090 100644 --- a/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss +++ b/frontend/src/app/logstash/logstash-filters/logstash-filter-create/logstash-filter-create.component.scss @@ -73,14 +73,23 @@ background-color: #f0f0f0 !important; } - // Syntax highlighting colors for YAML - .mtk5 { - color: #0277bd !important; // Keys - Blue + .mtk7 { + color: #2d7a1a !important; + font-weight: 600 !important; + } + + .mtk22 { + color: #0055cc !important; font-weight: 600 !important; } - .mtk1 { - color: #ff6b6b !important; // Values - Red + .mtk1, + .mtk5 { + color: #666666 !important; + } + + .mtk3 { + color: #c26e00 !important; } } } diff --git a/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.html b/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.html index 3780f27e1..e091c1b4d 100644 --- a/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.html +++ b/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.html @@ -16,6 +16,16 @@
-
-

+
+ +
+ + diff --git a/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.scss b/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.scss index 2025c4be1..f57c8e216 100644 --- a/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.scss +++ b/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.scss @@ -35,29 +35,6 @@ app-utm-modal-header { } } -.yaml-container { - width: 100%; - background: #ffffff; - padding: 16px; - border-radius: 6px; - border: 1px solid #e5e7eb; -} - -.yaml-preview { - white-space: pre-wrap; - font-family: "Fira Code", monospace; - font-size: 14px; -} - -::ng-deep .yaml-key { - color: #0277bd; - font-weight: 600; -} - -::ng-deep .yaml-value { - color: #FF6B6B; -} - .sub-header { background: transparent; border: none; @@ -110,3 +87,61 @@ app-utm-modal-header { } } +.rule-view-container { + border: 1px solid #d9d9d9; + border-radius: 4px; + overflow: hidden; + height: 600px; + width: 100%; + flex: 1; + + ::ng-deep { + .monaco-editor { + width: 100% !important; + height: 100% !important; + } + + .monaco-editor-background { + background-color: #f5f5f5 !important; + } + + .overflow-guard { + width: 100% !important; + height: 100% !important; + } + + .monaco-scrollable-element { + width: 100% !important; + height: 100% !important; + } + + .monaco-editor.readonly { + background-color: #f0f0f0 !important; + opacity: 0.8 !important; + } + + .monaco-editor.readonly .monaco-editor-background { + background-color: #f0f0f0 !important; + } + + .mtk7 { + color: #2d7a1a !important; + font-weight: 600 !important; + } + + .mtk22 { + color: #0055cc !important; + font-weight: 600 !important; + } + + .mtk1, + .mtk5 { + color: #666666 !important; + } + + .mtk3 { + color: #c26e00 !important; + } + } +} + diff --git a/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.ts b/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.ts index d9c0696ba..39bd08cc5 100644 --- a/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.ts +++ b/frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.ts @@ -8,14 +8,27 @@ import {Rule} from '../../../models/rule.model'; templateUrl: './rule-view.component.html', styleUrls: ['./rule-view.component.scss'], }) -export class RuleViewComponent implements OnInit { +export class RuleViewComponent { @Input() rowDocument: Rule; copied = false; - - ngOnInit() { - console.log('Rule received:', this.rowDocument); - } + editorOptions: any = { + theme: 'vs-light', + language: 'yaml', + automaticLayout: true, + fontSize: 13, + lineHeight: 24, + fontFamily: 'Courier New, Monaco, Menlo, monospace', + lineNumbers: 'on', + wordWrap: 'on', + minimap: { enabled: false }, + scrollBeyondLastLine: false, + formatOnPaste: true, + formatOnType: true, + tabSize: 2, + insertSpaces: true, + readOnly: true + }; get yamlString(): string { try { @@ -33,14 +46,6 @@ export class RuleViewComponent implements OnInit { } } - get yamlHighlighted(): string { - return this.yamlString - .replace(/^(\s*)([a-zA-Z0-9_]+):/gm, '$1$2:') - .replace(/: (.*)/g, ': $1') - .replace(/-\s+(.*)/g, '- $1') - .replace(/^\s{2,}(.+)/gm, ' $1'); - } - exportYaml() { const yamlContent = this.yamlString; diff --git a/frontend/src/app/rule-management/rule-management.module.ts b/frontend/src/app/rule-management/rule-management.module.ts index 6d4b76f2b..f3b5fe362 100644 --- a/frontend/src/app/rule-management/rule-management.module.ts +++ b/frontend/src/app/rule-management/rule-management.module.ts @@ -46,6 +46,7 @@ import {RuleService} from './services/rule.service'; import {RulesResolverService} from './services/rules.resolver.service'; import {GenericFilterComponent} from './share/generic-filter/generic-filter.component'; import {RuleDetailComponent} from "./app-rule/components/rule-list/components/rule-detail/rule-detail.component"; +import {MonacoEditorModule} from "ngx-monaco-editor"; @NgModule({ @@ -87,7 +88,8 @@ import {RuleDetailComponent} from "./app-rule/components/rule-list/components/ru NgSelectModule, InfiniteScrollModule, FileBrowserModule, - FormsModule + FormsModule, + MonacoEditorModule ], providers: [ RuleService, From f9e89930dcf03b03717d57fd8989efaa632f0f22 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 25 Feb 2026 09:21:17 -0600 Subject: [PATCH 138/240] feat: update Windows service paths for UTMStack agent Signed-off-by: Manuel Abascal --- .../src/app/app-module/guides/shared/constant.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/app-module/guides/shared/constant.ts b/frontend/src/app/app-module/guides/shared/constant.ts index 96c839d23..5a0a93036 100644 --- a/frontend/src/app/app-module/guides/shared/constant.ts +++ b/frontend/src/app/app-module/guides/shared/constant.ts @@ -42,7 +42,7 @@ export const createPlatforms = ( WINDOWS_SHELL, windowsPath, windowsRestart, [ - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_amd64.exe" ' + + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_windows_amd64.exe" ' + '-ArgumentList \'load-tls-certs\', \'[YOUR_CERT_PATH]\', \'[YOUR_KEY_PATH]\' ' + '-NoNewWindow -Wait' ] @@ -56,7 +56,7 @@ export const createPlatforms = ( windowsPath, windowsRestart, [ - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_arm64.exe" ' + + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_windows_arm64.exe" ' + '-ArgumentList \'load-tls-certs\', \'[YOUR_CERT_PATH]\', \'[YOUR_KEY_PATH]\' ' + '-NoNewWindow -Wait' ] @@ -112,10 +112,10 @@ export const createFileBeatsPlatforms = ( ]; export const PLATFORMS = createPlatforms( - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_amd64.exe " -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\', \'TLS\' -NoNewWindow -Wait\n', - 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_arm64.exe" -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\, \'TLS\' -NoNewWindow -Wait\n', - 'sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service_amd64 ACTION AGENT_NAME PROTOCOL TLS"', - 'sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service_arm64 ACTION AGENT_NAME PROTOCOL TLS"' + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_windows_amd64.exe " -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\', \'TLS\' -NoNewWindow -Wait\n', + 'Start-Process "C:\\Program Files\\UTMStack\\UTMStack Agent\\utmstack_agent_service_windows_arm64.exe" -ArgumentList \'ACTION\', \'AGENT_NAME\', \'PROTOCOL\, \'TLS\' -NoNewWindow -Wait\n', + 'sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service_linux_amd64 ACTION AGENT_NAME PROTOCOL TLS"', + 'sudo bash -c "/opt/utmstack-linux-agent/utmstack_agent_service_linux_arm64 ACTION AGENT_NAME PROTOCOL TLS"' ); export const FILEBEAT_PLATFORMS = createFileBeatsPlatforms( From 4b8e4d7cc8041b90feddb07d0d5a2104046cb868 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 25 Feb 2026 10:46:39 -0600 Subject: [PATCH 139/240] feat(db): add unique constraint on asset_name in utm_tenant_config table --- ...0260225001_add_unique_constraint_asset_name.xml | 14 ++++++++++++++ .../src/main/resources/config/liquibase/master.xml | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260225001_add_unique_constraint_asset_name.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260225001_add_unique_constraint_asset_name.xml b/backend/src/main/resources/config/liquibase/changelog/20260225001_add_unique_constraint_asset_name.xml new file mode 100644 index 000000000..cadac062f --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260225001_add_unique_constraint_asset_name.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 8603507cc..d37fad4e8 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -477,5 +477,7 @@ + + From be06aadebea70ea89608a151348ae102d03dd8b2 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 25 Feb 2026 13:42:44 -0600 Subject: [PATCH 140/240] feat(tenant-config): add findByAssetName method to retrieve UtmTenantConfig by asset name --- .../correlation/config/UtmTenantConfigRepository.java | 5 +++++ .../correlation/config/UtmTenantConfigService.java | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/backend/src/main/java/com/park/utmstack/repository/correlation/config/UtmTenantConfigRepository.java b/backend/src/main/java/com/park/utmstack/repository/correlation/config/UtmTenantConfigRepository.java index 34fe0e922..4719e200f 100644 --- a/backend/src/main/java/com/park/utmstack/repository/correlation/config/UtmTenantConfigRepository.java +++ b/backend/src/main/java/com/park/utmstack/repository/correlation/config/UtmTenantConfigRepository.java @@ -9,10 +9,15 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.Optional; + public interface UtmTenantConfigRepository extends JpaRepository, JpaSpecificationExecutor { + @Query(value = "SELECT t FROM UtmTenantConfig t WHERE" + "(:search IS NULL OR ((t.assetName LIKE :search OR lower(t.assetName) LIKE lower(:search))))") Page searchByFilters(@Param("search") String search, Pageable pageable); + + Optional findByAssetName(String assetName); } diff --git a/backend/src/main/java/com/park/utmstack/service/correlation/config/UtmTenantConfigService.java b/backend/src/main/java/com/park/utmstack/service/correlation/config/UtmTenantConfigService.java index 3f752c079..f80ca56ce 100644 --- a/backend/src/main/java/com/park/utmstack/service/correlation/config/UtmTenantConfigService.java +++ b/backend/src/main/java/com/park/utmstack/service/correlation/config/UtmTenantConfigService.java @@ -118,6 +118,15 @@ public Optional findOne(Long id) { } } + public Optional findByAssetName(String assetName) { + final String ctx = CLASSNAME + ".findByAssetName"; + try { + return utmTenantConfigRepository.findByAssetName(assetName); + } catch (Exception e) { + throw new RuntimeException(ctx + ": " + e.getMessage()); + } + } + /** * Get all UtmTenantConfig. * From 89d7e8cda29ebde4350410f20656147b51ef4d60 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 25 Feb 2026 14:23:39 -0600 Subject: [PATCH 141/240] feat(data-input-status): add methods to retrieve data input status by source and build sources list from tenant config --- .../UtmDataInputStatusRepository.java | 2 + .../service/UtmDataInputStatusService.java | 78 ++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/repository/datainput_ingestion/UtmDataInputStatusRepository.java b/backend/src/main/java/com/park/utmstack/repository/datainput_ingestion/UtmDataInputStatusRepository.java index 47be50866..a167dde27 100644 --- a/backend/src/main/java/com/park/utmstack/repository/datainput_ingestion/UtmDataInputStatusRepository.java +++ b/backend/src/main/java/com/park/utmstack/repository/datainput_ingestion/UtmDataInputStatusRepository.java @@ -65,4 +65,6 @@ and lower(trim(ds.dataType)) != lower(trim(:dataType)) @Query("SELECT s FROM UtmDataInputStatus s WHERE s.source = :ip OR s.source = :hostname") List findByIpOrHostname(@Param("ip") String ip, @Param("hostname") String hostname); + + Optional findBySourceIsIn(List sources); } diff --git a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java index 7bbd975f6..fa9559d7c 100644 --- a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java +++ b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java @@ -1,6 +1,7 @@ package com.park.utmstack.service; import com.park.utmstack.config.Constants; +import com.park.utmstack.domain.correlation.config.UtmTenantConfig; import com.park.utmstack.domain.datainput_ingestion.UtmDataInputStatus; import com.park.utmstack.domain.UtmServerModule; import com.park.utmstack.domain.application_events.enums.ApplicationEventType; @@ -17,6 +18,7 @@ import com.park.utmstack.repository.correlation.config.UtmDataTypesRepository; import com.park.utmstack.repository.network_scan.UtmNetworkScanRepository; import com.park.utmstack.service.application_events.ApplicationEventService; +import com.park.utmstack.service.correlation.config.UtmTenantConfigService; import com.park.utmstack.service.elasticsearch.ElasticsearchService; import com.park.utmstack.service.elasticsearch.SearchUtil; import com.park.utmstack.service.logstash_pipeline.response.statistic.StatisticDocument; @@ -24,6 +26,7 @@ import com.park.utmstack.service.network_scan.UtmNetworkScanService; import com.park.utmstack.util.enums.AlertSeverityEnum; import com.park.utmstack.util.enums.AlertStatus; +import com.park.utmstack.util.exceptions.ApiException; import lombok.RequiredArgsConstructor; import org.apache.http.conn.util.InetAddressUtils; import org.opensearch.client.json.JsonData; @@ -34,6 +37,7 @@ import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -68,6 +72,7 @@ public class UtmDataInputStatusService { private final UtmDataTypesRepository dataTypesRepository; private final UtmNetworkScanRepository networkScanRepository; private final UtmDataInputStatusCheckpointRepository checkpointRepository; + private final UtmTenantConfigService utmTenantConfigService; /** @@ -174,7 +179,7 @@ private void checkDataInputStatus(List inputs, String server } } - @Scheduled(fixedDelay = 15000, initialDelay = 30000) + @Scheduled(fixedDelay = 1000, initialDelay = 2000) public void syncDataInputStatus() { final String ctx = CLASSNAME + ".syncDataInputStatus"; @@ -193,7 +198,7 @@ public void syncDataInputStatus() { latestStats.forEach((key, stat) -> { try { String dataType = stat.getDataType(); - String dataSource = stat.getDataSource(); + String dataSource = getDataSource(stat.getDataSource()); long timestamp = Instant.parse(stat.getTimestamp()).getEpochSecond(); String compositeKey = dataType + "-" + dataSource; @@ -210,7 +215,9 @@ public void syncDataInputStatus() { .median(86400L) .build(); changed = true; + } else if (status.getTimestamp() != timestamp) { + status.setSource(dataSource); status.setTimestamp(timestamp); changed = true; } @@ -497,4 +504,71 @@ private Map getLatestStatisticsByDataSource() { return result; } + private String getDataSource(String assetName) { + final String ctx = CLASSNAME + ".getDataSource"; + + Optional tenantConfig = this.utmTenantConfigService.findByAssetName(assetName); + + if (tenantConfig.isEmpty()) { + return assetName; + } + + List sources = buildSourcesList(tenantConfig.get()); + + if (CollectionUtils.isEmpty(sources)) { + return assetName; + } + + Optional dataInputStatus = this.findDataInputBySource(sources); + + return dataInputStatus + .map(UtmDataInputStatus::getSource) + .orElse(assetName); + } + + /** + * Builds a combined list of hostnames and IPs from tenant configuration + * + * @param tenantConfig the tenant configuration + * @return combined list of sources, or empty list if none available + */ + private List buildSourcesList(UtmTenantConfig tenantConfig) { + List sources = new ArrayList<>(); + + if (!CollectionUtils.isEmpty(tenantConfig.getAssetHostnameList())) { + sources.addAll(tenantConfig.getAssetHostnameList()); + } + + if (!CollectionUtils.isEmpty(tenantConfig.getAssetIpList())) { + sources.addAll(tenantConfig.getAssetIpList()); + } + + return sources; + } + + /** + * Finds a data input status by searching in the provided list of sources. + * Returns the first available source from the database. + * + * @param sources list of source hostnames/IPs to search for + * @return Optional containing the data input status if found + */ + public Optional findDataInputBySource(List sources) { + final String ctx = CLASSNAME + ".findDataInputBySource"; + + if (CollectionUtils.isEmpty(sources)) { + return Optional.empty(); + } + + try { + return this.dataInputStatusRepository.findBySourceIsIn(sources); + + } catch (Exception ex) { + log.error("{}: Error finding data input status by source {} - {}", + ctx, sources, ex.getMessage(), ex); + return Optional.empty(); + } + } + + } From 57f38e6a1fc31502e718b2c8ebaf710e53462935 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 25 Feb 2026 14:25:43 -0600 Subject: [PATCH 142/240] feat(data-input-status): add methods to retrieve data input status by source and build sources list from tenant config --- .../com/park/utmstack/service/UtmDataInputStatusService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java index fa9559d7c..5e7f3c904 100644 --- a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java +++ b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java @@ -179,7 +179,7 @@ private void checkDataInputStatus(List inputs, String server } } - @Scheduled(fixedDelay = 1000, initialDelay = 2000) + @Scheduled(fixedDelay = 15000, initialDelay = 30000) public void syncDataInputStatus() { final String ctx = CLASSNAME + ".syncDataInputStatus"; From 49b3d6c2eea61729bc1733a6c31da533dde713ef Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 25 Feb 2026 15:33:42 -0600 Subject: [PATCH 143/240] feat(data-input-status): add methods to retrieve data input status by source and build sources list from tenant config --- .../service/UtmDataInputStatusService.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java index 5e7f3c904..64d9781df 100644 --- a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java +++ b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java @@ -179,7 +179,7 @@ private void checkDataInputStatus(List inputs, String server } } - @Scheduled(fixedDelay = 15000, initialDelay = 30000) + @Scheduled(fixedDelay = 1000, initialDelay = 2000) public void syncDataInputStatus() { final String ctx = CLASSNAME + ".syncDataInputStatus"; @@ -198,10 +198,11 @@ public void syncDataInputStatus() { latestStats.forEach((key, stat) -> { try { String dataType = stat.getDataType(); - String dataSource = getDataSource(stat.getDataSource()); + String assetName = this.getDataSource(stat.getDataSource()); + long timestamp = Instant.parse(stat.getTimestamp()).getEpochSecond(); - String compositeKey = dataType + "-" + dataSource; + String compositeKey = dataType + "-" + assetName; UtmDataInputStatus status = existing.get(compositeKey); boolean changed = false; @@ -210,14 +211,18 @@ public void syncDataInputStatus() { status = UtmDataInputStatus.builder() .id(compositeKey) .dataType(dataType) - .source(dataSource) + .source(assetName) .timestamp(timestamp) .median(86400L) .build(); changed = true; } else if (status.getTimestamp() != timestamp) { - status.setSource(dataSource); + + if (!assetName.equals(stat.getDataSource())) { + status.setSource(stat.getDataSource()); + } + status.setTimestamp(timestamp); changed = true; } From bc6f3f560cb45e648e94b34f2f03855dfb88ba00 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 26 Feb 2026 08:04:43 -0600 Subject: [PATCH 144/240] feat(data-input-status): add methods to retrieve data input status by source and build sources list from tenant config --- .../com/park/utmstack/service/UtmDataInputStatusService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java index 64d9781df..46f69bd0e 100644 --- a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java +++ b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java @@ -179,7 +179,7 @@ private void checkDataInputStatus(List inputs, String server } } - @Scheduled(fixedDelay = 1000, initialDelay = 2000) + @Scheduled(fixedDelay = 15000, initialDelay = 30000) public void syncDataInputStatus() { final String ctx = CLASSNAME + ".syncDataInputStatus"; From f8a8aff65cb5e7714a8bb860c6375280532c76dc Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 26 Feb 2026 11:29:12 -0600 Subject: [PATCH 145/240] feat(data-input-status): add alias column and update logic for data input status --- .../UtmDataInputStatus.java | 4 ++ .../service/UtmDataInputStatusService.java | 51 +++++++++++++------ ...0226001_add_alias_to_data_input_status.xml | 21 ++++++++ .../resources/config/liquibase/master.xml | 2 + 4 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260226001_add_alias_to_data_input_status.xml diff --git a/backend/src/main/java/com/park/utmstack/domain/datainput_ingestion/UtmDataInputStatus.java b/backend/src/main/java/com/park/utmstack/domain/datainput_ingestion/UtmDataInputStatus.java index 8a4dd7046..19a94835e 100644 --- a/backend/src/main/java/com/park/utmstack/domain/datainput_ingestion/UtmDataInputStatus.java +++ b/backend/src/main/java/com/park/utmstack/domain/datainput_ingestion/UtmDataInputStatus.java @@ -43,6 +43,10 @@ public class UtmDataInputStatus implements Serializable { @Column(name = "median") private Long median; + @Size(max = 500) + @Column(name = "alias", length = 500, nullable = true) + private String alias; + /** * Define if a source is down or up. * Null is returned when the calculation could not be done. diff --git a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java index 46f69bd0e..5f44ea0b0 100644 --- a/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java +++ b/backend/src/main/java/com/park/utmstack/service/UtmDataInputStatusService.java @@ -26,7 +26,6 @@ import com.park.utmstack.service.network_scan.UtmNetworkScanService; import com.park.utmstack.util.enums.AlertSeverityEnum; import com.park.utmstack.util.enums.AlertStatus; -import com.park.utmstack.util.exceptions.ApiException; import lombok.RequiredArgsConstructor; import org.apache.http.conn.util.InetAddressUtils; import org.opensearch.client.json.JsonData; @@ -37,7 +36,6 @@ import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -198,11 +196,14 @@ public void syncDataInputStatus() { latestStats.forEach((key, stat) -> { try { String dataType = stat.getDataType(); - String assetName = this.getDataSource(stat.getDataSource()); + String statName = stat.getDataSource(); + String sourceWithAlias = this.getSourceName(statName); + String resolvedAlias = sourceWithAlias != null ? statName : null; + String source = sourceWithAlias == null ? statName : sourceWithAlias; long timestamp = Instant.parse(stat.getTimestamp()).getEpochSecond(); - String compositeKey = dataType + "-" + assetName; + String compositeKey = dataType + "-" + source; UtmDataInputStatus status = existing.get(compositeKey); boolean changed = false; @@ -211,19 +212,16 @@ public void syncDataInputStatus() { status = UtmDataInputStatus.builder() .id(compositeKey) .dataType(dataType) - .source(assetName) + .source(source) + .alias(resolvedAlias) .timestamp(timestamp) .median(86400L) .build(); changed = true; - } else if (status.getTimestamp() != timestamp) { - - if (!assetName.equals(stat.getDataSource())) { - status.setSource(stat.getDataSource()); - } - + } else if (status.getTimestamp() != timestamp || !Objects.equals(status.getAlias(), resolvedAlias)) { status.setTimestamp(timestamp); + status.setAlias(resolvedAlias); changed = true; } @@ -280,6 +278,7 @@ public void synchronizeSourcesToAssets() { } Map sourcesWithStatus = extractSourcesWithUpDownStatus(sources); + Map sourcesWithAlias = extractSourcesWithAlias(sources); List keys = new ArrayList<>(sourcesWithStatus.keySet()); List assets = networkScanRepository.findByAssetIpInOrAssetNameIn(keys, keys); @@ -293,12 +292,23 @@ public void synchronizeSourcesToAssets() { for (Map.Entry entry : sourcesWithStatus.entrySet()) { String key = entry.getKey(); + String alias = sourcesWithAlias.get(key); Boolean alive = entry.getValue(); + UtmNetworkScan asset = assetsByKey.get(key); + if (asset == null && StringUtils.hasText(alias)) { + asset = assetsByKey.get(alias); + } + if (asset != null) { if (asset.getUpdateLevel() == null || asset.getUpdateLevel().equals(UpdateLevel.DATASOURCE) || asset.getUpdateLevel().equals(UpdateLevel.AGENT)) { + + if (StringUtils.hasText(alias) && !alias.equals(asset.getAssetAlias())) { + asset.assetAliases(alias); + } + asset.assetAlive(alive) .updateLevel(UpdateLevel.DATASOURCE) .assetStatus(AssetStatus.CHECK) @@ -332,6 +342,17 @@ public void synchronizeSourcesToAssets() { } } + private Map extractSourcesWithAlias(List sources) { + Map alias = new HashMap<>(); + + sources.forEach(src -> { + if (StringUtils.hasText(src.getAlias())) { + alias.put(src.getSource(), src.getAlias()); + } + }); + return alias; + } + private Map extractSourcesWithUpDownStatus(List sources) { Map upDown = new HashMap<>(); sources.forEach(src -> { @@ -509,26 +530,26 @@ private Map getLatestStatisticsByDataSource() { return result; } - private String getDataSource(String assetName) { + private String getSourceName(String assetName) { final String ctx = CLASSNAME + ".getDataSource"; Optional tenantConfig = this.utmTenantConfigService.findByAssetName(assetName); if (tenantConfig.isEmpty()) { - return assetName; + return null; } List sources = buildSourcesList(tenantConfig.get()); if (CollectionUtils.isEmpty(sources)) { - return assetName; + return null; } Optional dataInputStatus = this.findDataInputBySource(sources); return dataInputStatus .map(UtmDataInputStatus::getSource) - .orElse(assetName); + .orElse(null); } /** diff --git a/backend/src/main/resources/config/liquibase/changelog/20260226001_add_alias_to_data_input_status.xml b/backend/src/main/resources/config/liquibase/changelog/20260226001_add_alias_to_data_input_status.xml new file mode 100644 index 000000000..da6b01924 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260226001_add_alias_to_data_input_status.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index d37fad4e8..21a9c36ab 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -479,5 +479,7 @@ + + From 8876d2f3985d76efa63943fa9a156ff6a4d78bd4 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 26 Feb 2026 12:59:52 -0600 Subject: [PATCH 146/240] feat: remove alert from addTag function in fields-selector component Signed-off-by: Manuel Abascal --- .../components/fields-selector/fields-selector.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/app/rule-management/app-rule/components/fields-selector/fields-selector.component.ts b/frontend/src/app/rule-management/app-rule/components/fields-selector/fields-selector.component.ts index 2e532a690..e671f6322 100644 --- a/frontend/src/app/rule-management/app-rule/components/fields-selector/fields-selector.component.ts +++ b/frontend/src/app/rule-management/app-rule/components/fields-selector/fields-selector.component.ts @@ -39,7 +39,6 @@ export class FieldsSelectorComponent implements OnInit { } addTag(name: string) { - alert(name) return { name }; } } From 5158938448abcc0525174d3ba12a53ce93fb7669 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 27 Feb 2026 09:43:18 -0600 Subject: [PATCH 147/240] fix: deprecate enable parameter in TFA section Signed-off-by: Manuel Abascal --- .../app-config-sections/app-config-sections.component.html | 2 +- .../app-config-sections/app-config-sections.component.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/shared/components/utm/config/app-config-sections/app-config-sections.component.html b/frontend/src/app/shared/components/utm/config/app-config-sections/app-config-sections.component.html index 2ed339dbc..bc3288290 100644 --- a/frontend/src/app/shared/components/utm/config/app-config-sections/app-config-sections.component.html +++ b/frontend/src/app/shared/components/utm/config/app-config-sections/app-config-sections.component.html @@ -28,7 +28,7 @@
{{section.section | titlecase}}
class="mr-2" *ngIf="section.shortName === sectionType[sectionType.EMAIL]"> - c.confParamShort !== 'utmstack.tfa.enable'); + if (this.isOnline && this.section.id === this.sectionType.INSTANCE_REGISTRATION) { return this.configs.filter( c => c.confParamShort !== 'utmstack.instance.auth'); } else { @@ -328,10 +331,6 @@ export class AppConfigSectionsComponent implements OnInit, OnDestroy { } } - isEnabledTfa(): boolean { - return this.configs.some(conf => conf.confParamShort === 'utmstack.tfa.enable' && conf.confParamValue === 'true'); - } - serializeConfigValue(conf: SectionConfigParamType): string { if (conf.confParamDatatype === ConfigDataTypeEnum.Cron) { return JSON.stringify(conf.confParamValue); From 2219cfa96188c3f727ddaac41f417bc70654325d Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 27 Feb 2026 10:05:15 -0600 Subject: [PATCH 148/240] feat(.gitignore): add .env file to ignore list --- backend/.gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/.gitignore b/backend/.gitignore index 518edd788..95d319e15 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -144,3 +144,9 @@ Desktop.ini ###################### .eslintcache init-utmstack-backend.bat + +###################### +# ENV +###################### + +.env/ From 7e28b0a3a880e60e6d059b895951bc2c74c2a5d8 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 2 Mar 2026 07:34:04 -0600 Subject: [PATCH 149/240] fix: update winevent correlation rules --- ...001_update_winevent_correlation_rules.xml} | 6 +- .../utm_correlation_rules.sql | 2108 ++++++++--------- .../utm_group_rules_data_type.sql | 242 +- .../resources/config/liquibase/master.xml | 2 +- 4 files changed, 1179 insertions(+), 1179 deletions(-) rename backend/src/main/resources/config/liquibase/changelog/{20260223002_update_winevent_correlation_rules.xml => 20260302001_update_winevent_correlation_rules.xml} (85%) rename backend/src/main/resources/config/liquibase/data/{20260223 => 20260302}/utm_correlation_rules.sql (88%) rename backend/src/main/resources/config/liquibase/data/{20260223 => 20260302}/utm_group_rules_data_type.sql (99%) diff --git a/backend/src/main/resources/config/liquibase/changelog/20260223002_update_winevent_correlation_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260302001_update_winevent_correlation_rules.xml similarity index 85% rename from backend/src/main/resources/config/liquibase/changelog/20260223002_update_winevent_correlation_rules.xml rename to backend/src/main/resources/config/liquibase/changelog/20260302001_update_winevent_correlation_rules.xml index 77917533f..9a56a4ac3 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20260223002_update_winevent_correlation_rules.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20260302001_update_winevent_correlation_rules.xml @@ -4,7 +4,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"> - +
diff --git a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts index 399158384..fca5fc706 100644 --- a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts +++ b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts @@ -1,12 +1,11 @@ import {ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core'; import {Subject} from 'rxjs'; -import {debounceTime, distinctUntilChanged, finalize, map, takeUntil, tap} from 'rxjs/operators'; +import {debounceTime, finalize, map, takeUntil, tap} from 'rxjs/operators'; import {ModalService} from '../../../core/modal/modal.service'; import {UtmToastService} from '../../../shared/alert/utm-toast.service'; import { ModalConfirmationComponent } from '../../../shared/components/utm/util/modal-confirmation/modal-confirmation.component'; -import {EncryptService} from '../../../shared/services/util/encrypt.service'; import {ModuleChangeStatusBehavior} from '../../shared/behavior/module-change-status.behavior'; import {IntCreateGroupComponent} from '../../shared/components/int-create-group/int-create-group.component'; import {GroupTypeEnum} from '../../shared/enum/group-type.enum'; @@ -46,10 +45,10 @@ export class IntGenericGroupConfigComponent implements OnInit, OnChanges, OnDest destroy$ = new Subject(); uniqueConfigNameConstrain = false; invalidDomainOrIp = false; + savingConfigs = new Map(); constructor(private utmModuleGroupService: UtmModuleGroupService, private toast: UtmToastService, - private encryptService: EncryptService, private utmModuleGroupConfService: UtmModuleGroupConfService, private modalService: ModalService, private moduleChangeStatusBehavior: ModuleChangeStatusBehavior, @@ -189,7 +188,7 @@ export class IntGenericGroupConfigComponent implements OnInit, OnChanges, OnDest } saveConfig(group: UtmModuleGroupType) { - this.savingConfig = true; + this.savingConfigs.set(group.id, true); const configs = this.changes.keys.filter(change => change.groupId === group.id); this.utmModuleGroupConfService.update({ @@ -197,7 +196,7 @@ export class IntGenericGroupConfigComponent implements OnInit, OnChanges, OnDest keys: configs }).pipe( finalize(() => { - this.savingConfig = false; + this.savingConfigs.set(group.id, false); this.cdr.detectChanges(); }) ).subscribe({ @@ -440,6 +439,7 @@ export class IntGenericGroupConfigComponent implements OnInit, OnChanges, OnDest this.addChange(integrationConfig); } + ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); From 313e44681148ab66aba1e375867c3dd7e94204af Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Wed, 4 Mar 2026 08:12:44 -0600 Subject: [PATCH 177/240] fix(asset-sync): adjust scheduling parameters for data synchronization --- .../service/network_scan/AssetSynchronizationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/park/utmstack/service/network_scan/AssetSynchronizationService.java b/backend/src/main/java/com/park/utmstack/service/network_scan/AssetSynchronizationService.java index 6e66bcf74..becd8125e 100644 --- a/backend/src/main/java/com/park/utmstack/service/network_scan/AssetSynchronizationService.java +++ b/backend/src/main/java/com/park/utmstack/service/network_scan/AssetSynchronizationService.java @@ -39,7 +39,7 @@ public class AssetSynchronizationService { private final UtmTenantConfigService tenantConfigService; @Transactional - @Scheduled(fixedDelay = 30000, initialDelay = 60000) + @Scheduled(fixedDelay = 60000, initialDelay = 120000) public void syncDataInputsAndAssets() { String correlationId = UUID.randomUUID().toString().substring(0, 8); From 33fc3d4897ecd64a68c2b7f5215f90d84cd6225f Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Thu, 5 Mar 2026 08:46:52 -0600 Subject: [PATCH 178/240] fix: remove duplicate imports in int-generic-group-config component Signed-off-by: Manuel Abascal --- .../int-generic-group-config.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts index 3db9b3d69..8e54d0a24 100644 --- a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts +++ b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts @@ -1,7 +1,6 @@ import {ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core'; import {Subject} from 'rxjs'; import {debounceTime, finalize, takeUntil, tap} from 'rxjs/operators'; -import {debounceTime, finalize, map, takeUntil, tap} from 'rxjs/operators'; import {ModalService} from '../../../core/modal/modal.service'; import {UtmToastService} from '../../../shared/alert/utm-toast.service'; import { From 998878f355085bf6ee7fe8c0d049ec02fbfb0a43 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 6 Mar 2026 10:33:26 -0600 Subject: [PATCH 179/240] feat: implement password reset functionality with expiration handling and logging --- .../enums/ApplicationEventType.java | 2 + .../park/utmstack/service/UserService.java | 36 +++++++++++---- .../CurrentUserLoginNotFoundException.java | 6 ++- .../utmstack/web/rest/AccountResource.java | 45 ++++++++----------- .../rest/errors/ResetKeyExpiredException.java | 11 +++++ 5 files changed, 62 insertions(+), 38 deletions(-) create mode 100644 backend/src/main/java/com/park/utmstack/web/rest/errors/ResetKeyExpiredException.java diff --git a/backend/src/main/java/com/park/utmstack/domain/application_events/enums/ApplicationEventType.java b/backend/src/main/java/com/park/utmstack/domain/application_events/enums/ApplicationEventType.java index eca45ec19..fcd56f95a 100644 --- a/backend/src/main/java/com/park/utmstack/domain/application_events/enums/ApplicationEventType.java +++ b/backend/src/main/java/com/park/utmstack/domain/application_events/enums/ApplicationEventType.java @@ -46,5 +46,7 @@ public enum ApplicationEventType { MODULE_ACTIVATION_SUCCESS, API_KEY_ACCESS_SUCCESS, API_KEY_ACCESS_FAILURE, + RESET_USER_PASSWORD_ATTEMPT, + RESET_USER_PASSWORD_SUCCESS, UNDEFINED } diff --git a/backend/src/main/java/com/park/utmstack/service/UserService.java b/backend/src/main/java/com/park/utmstack/service/UserService.java index 5dacde192..b50b5feb6 100644 --- a/backend/src/main/java/com/park/utmstack/service/UserService.java +++ b/backend/src/main/java/com/park/utmstack/service/UserService.java @@ -14,6 +14,7 @@ import com.park.utmstack.util.exceptions.CurrentUserLoginNotFoundException; import com.park.utmstack.web.rest.errors.BadRequestAlertException; import com.park.utmstack.web.rest.errors.InvalidPasswordException; +import com.park.utmstack.web.rest.errors.ResetKeyExpiredException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -79,15 +80,32 @@ public void init() { } } - public Optional completePasswordReset(String newPassword, String key) { - log.debug("Reset user password for reset key {}", key); - return userRepository.findOneByResetKey(key).filter( - user -> user.getResetDate().isAfter(Instant.now().minusSeconds(86400))).map(user -> { - user.setPassword(passwordEncoder.encode(newPassword)); - user.setResetKey(null); - user.setResetDate(null); - return user; - }); + public User completePasswordReset(String newPassword, String key) { + final String ctx = CLASS_NAME + ".completePasswordReset"; + log.debug("{}: Processing password reset with key: {}", ctx, key); + + Optional userOptional = userRepository.findOneByResetKey(key); + + if (userOptional.isEmpty()) { + log.info("{}: No user found with reset key", ctx); + throw new CurrentUserLoginNotFoundException("No user found with reset key"); + } + + User user = userOptional.get(); + Instant resetDeadline = Instant.now().minusSeconds(86400); + + if (!user.getResetDate().isAfter(resetDeadline)) { + log.error("{}: Reset key expired for user: {}", ctx, user.getLogin()); + throw new ResetKeyExpiredException(String.format("Reset key expired for user: %s", user.getLogin())); + } + + user.setPassword(passwordEncoder.encode(newPassword)); + user.setResetKey(null); + user.setResetDate(null); + user.setActivated(true); + + log.info("{}: Password reset completed successfully for user: {}", ctx, user.getLogin()); + return user; } public Optional requestPasswordReset(String mail) { diff --git a/backend/src/main/java/com/park/utmstack/util/exceptions/CurrentUserLoginNotFoundException.java b/backend/src/main/java/com/park/utmstack/util/exceptions/CurrentUserLoginNotFoundException.java index edc0d38a7..7ee8ad0b2 100644 --- a/backend/src/main/java/com/park/utmstack/util/exceptions/CurrentUserLoginNotFoundException.java +++ b/backend/src/main/java/com/park/utmstack/util/exceptions/CurrentUserLoginNotFoundException.java @@ -1,7 +1,9 @@ package com.park.utmstack.util.exceptions; -public class CurrentUserLoginNotFoundException extends RuntimeException { +import org.springframework.http.HttpStatus; + +public class CurrentUserLoginNotFoundException extends ApiException { public CurrentUserLoginNotFoundException(String message) { - super(message); + super(message, HttpStatus.NOT_FOUND); } } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/AccountResource.java b/backend/src/main/java/com/park/utmstack/web/rest/AccountResource.java index ec9ab0fe4..219c85241 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/AccountResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/AccountResource.java @@ -1,6 +1,8 @@ package com.park.utmstack.web.rest; +import com.park.utmstack.aop.logging.AuditEvent; +import com.park.utmstack.aop.logging.Loggable; import com.park.utmstack.domain.User; import com.park.utmstack.domain.application_events.enums.ApplicationEventType; import com.park.utmstack.repository.UserRepository; @@ -70,7 +72,7 @@ public ResponseEntity isAuthenticated(HttpServletRequest request) { log.error(msg); applicationEventService.createEvent(msg, ApplicationEventType.ERROR); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( - HeaderUtil.createFailureAlert("", "", msg)).body(null); + HeaderUtil.createFailureAlert("", "", msg)).body(null); } } @@ -85,8 +87,8 @@ public UserDTO getAccount() { final String ctx = CLASSNAME + ".getAccount"; try { return userService.getUserWithAuthorities() - .map(UserDTO::new) - .orElseThrow(() -> new InternalServerErrorException("User could not be found")); + .map(UserDTO::new) + .orElseThrow(() -> new InternalServerErrorException("User could not be found")); } catch (InternalServerErrorException e) { String msg = ctx + ": " + e.getMessage(); log.error(msg); @@ -116,7 +118,7 @@ public void saveAccount(@Valid @RequestBody UserDTO userDTO) { throw new InternalServerErrorException("User could not be found"); userService.updateUser(userDTO.getFirstName(), userDTO.getLastName(), userDTO.getEmail(), - userDTO.getLangKey(), userDTO.getImageUrl()); + userDTO.getLangKey(), userDTO.getImageUrl()); } catch (Exception e) { String msg = ctx + ": " + e.getMessage(); log.error(msg); @@ -156,8 +158,8 @@ public void requestPasswordReset(@RequestBody String mail) { final String ctx = CLASSNAME + ".requestPasswordReset"; try { mailService.sendPasswordResetMail( - userService.requestPasswordReset(mail) - .orElseThrow(EmailNotFoundException::new)); + userService.requestPasswordReset(mail) + .orElseThrow(EmailNotFoundException::new)); } catch (Exception e) { String msg = ctx + ": " + e.getMessage(); log.error(msg); @@ -166,34 +168,23 @@ public void requestPasswordReset(@RequestBody String mail) { } } - /** - * POST /account/reset-password/finish : Finish to reset the password of the user - * - * @param keyAndPassword the generated key and the new password - * @throws InvalidPasswordException 400 (Bad Request) if the password is incorrect - * @throws RuntimeException 500 (Internal Server Error) if the password could not be reset - */ + @AuditEvent( + attemptType = ApplicationEventType.RESET_USER_PASSWORD_ATTEMPT, + attemptMessage = "Attempt to reset user password initiated", + successType = ApplicationEventType.RESET_USER_PASSWORD_SUCCESS, + successMessage = "User password reset successfully" + ) @PostMapping(path = "/account/reset-password/finish") public void finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) { - final String ctx = CLASSNAME + ".finishPasswordReset"; - try { - validatePasswordLength(keyAndPassword.getNewPassword()); - Optional user = - userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey()); - if (user.isEmpty()) - throw new InternalServerErrorException("No user was found for this reset key"); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - throw new RuntimeException(msg); - } + validatePasswordLength(keyAndPassword.getNewPassword()); + userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey()); + } private void validatePasswordLength(String password) { if (!StringUtils.hasText(password) || password.length() < ManagedUserVM.PASSWORD_MIN_LENGTH || - password.length() > ManagedUserVM.PASSWORD_MAX_LENGTH) + password.length() > ManagedUserVM.PASSWORD_MAX_LENGTH) throw new InvalidPasswordException(); } } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/errors/ResetKeyExpiredException.java b/backend/src/main/java/com/park/utmstack/web/rest/errors/ResetKeyExpiredException.java new file mode 100644 index 000000000..4297e20c1 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/web/rest/errors/ResetKeyExpiredException.java @@ -0,0 +1,11 @@ +package com.park.utmstack.web.rest.errors; + +import com.park.utmstack.util.exceptions.ApiException; +import org.springframework.http.HttpStatus; + +public class ResetKeyExpiredException extends ApiException { + + public ResetKeyExpiredException(String message) { + super(message, HttpStatus.BAD_REQUEST); + } +} From c588e496c49941a2e693f581df5044f7bda25000 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 6 Mar 2026 10:43:48 -0600 Subject: [PATCH 180/240] feat: implement password reset functionality with expiration handling and logging --- .../src/main/java/com/park/utmstack/service/UserService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/UserService.java b/backend/src/main/java/com/park/utmstack/service/UserService.java index b50b5feb6..6d3736df7 100644 --- a/backend/src/main/java/com/park/utmstack/service/UserService.java +++ b/backend/src/main/java/com/park/utmstack/service/UserService.java @@ -80,7 +80,7 @@ public void init() { } } - public User completePasswordReset(String newPassword, String key) { + public void completePasswordReset(String newPassword, String key) { final String ctx = CLASS_NAME + ".completePasswordReset"; log.debug("{}: Processing password reset with key: {}", ctx, key); @@ -105,7 +105,6 @@ public User completePasswordReset(String newPassword, String key) { user.setActivated(true); log.info("{}: Password reset completed successfully for user: {}", ctx, user.getLogin()); - return user; } public Optional requestPasswordReset(String mail) { From e006db874549e58e821fd5b8048e4178ddc5a282 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 6 Mar 2026 11:14:56 -0600 Subject: [PATCH 181/240] feat: implement password reset functionality with expiration handling and logging --- .../main/java/com/park/utmstack/service/UserService.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/UserService.java b/backend/src/main/java/com/park/utmstack/service/UserService.java index 6d3736df7..04736f5b9 100644 --- a/backend/src/main/java/com/park/utmstack/service/UserService.java +++ b/backend/src/main/java/com/park/utmstack/service/UserService.java @@ -88,7 +88,9 @@ public void completePasswordReset(String newPassword, String key) { if (userOptional.isEmpty()) { log.info("{}: No user found with reset key", ctx); - throw new CurrentUserLoginNotFoundException("No user found with reset key"); + throw new CurrentUserLoginNotFoundException( + "The password reset link is invalid or no longer exists. Please request a new password reset." + ); } User user = userOptional.get(); @@ -96,7 +98,9 @@ public void completePasswordReset(String newPassword, String key) { if (!user.getResetDate().isAfter(resetDeadline)) { log.error("{}: Reset key expired for user: {}", ctx, user.getLogin()); - throw new ResetKeyExpiredException(String.format("Reset key expired for user: %s", user.getLogin())); + throw new ResetKeyExpiredException( + "The password reset link has expired. Password reset links are valid for 24 hours. Please request a new one." + ); } user.setPassword(passwordEncoder.encode(newPassword)); From 54495a741901d9542da09b115334fdaa0fc1fb12 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 6 Mar 2026 10:13:23 -0600 Subject: [PATCH 182/240] feat: update layout for password reset component Signed-off-by: Manuel Abascal --- .../finish/password-reset-finish-component.scss | 7 +++++-- .../finish/password-reset-finish.component.html | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish-component.scss b/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish-component.scss index 797f122f9..2b212590d 100644 --- a/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish-component.scss +++ b/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish-component.scss @@ -1,5 +1,4 @@ -@import "../../../../../../assets/styles/theme"; -@import "../../../../../../assets/styles/var"; +@import "../../../../../../assets/modules/auth"; .reset-pass-container { .card { @@ -36,3 +35,7 @@ } } +.form-control { + padding: .375rem .75rem !important; +} + diff --git a/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.html b/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.html index 03ea39b52..1930ac377 100644 --- a/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.html +++ b/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.html @@ -1,5 +1,5 @@
-
+
Password reset
From 868677f36a1e3787d3179e9afe953ad1b93f2052 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 6 Mar 2026 10:13:32 -0600 Subject: [PATCH 183/240] feat: enhance password reset logic to handle missing key scenario Signed-off-by: Manuel Abascal --- .../finish/password-reset-finish.component.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.ts b/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.ts index 7cbfd227a..f9cd99ec9 100644 --- a/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.ts +++ b/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.ts @@ -23,7 +23,6 @@ export class PasswordResetFinishComponent implements OnInit, AfterViewInit { constructor( private passwordResetFinishService: PasswordResetFinishService, - private loginModalService: ModalService, private route: ActivatedRoute, private router: Router, private elementRef: ElementRef, @@ -33,10 +32,14 @@ export class PasswordResetFinishComponent implements OnInit, AfterViewInit { ngOnInit() { this.route.queryParams.subscribe(params => { - this.key = params.key; + if (params && params.key) { + this.key = params.key; + this.keyMissing = false + } else { + this.keyMissing = true; + } }); this.resetAccount = {}; - this.keyMissing = !this.key; } ngAfterViewInit() { From f3b6f9e9965d587b370734f11a8a5fc135c26265 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 6 Mar 2026 11:36:35 -0600 Subject: [PATCH 184/240] feat: improve password reset feedback and add back to login button Signed-off-by: Manuel Abascal --- .../password-reset-finish.component.html | 18 +++++++++++++----- .../finish/password-reset-finish.component.ts | 6 ++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.html b/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.html index 1930ac377..6439040f7 100644 --- a/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.html +++ b/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.html @@ -13,13 +13,13 @@
Password reset
The password reset key is missing.
- Your password couldn't be reset. Remember a password request is only valid for 24 hours. + {{ error }}

- Your password has been reset. Please - sign in. + Your password has been reset.

-
+
The password and its confirmation do not match!
@@ -72,12 +72,20 @@
Password reset
- +
+ +
diff --git a/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.ts b/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.ts index f9cd99ec9..512d2b64f 100644 --- a/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.ts +++ b/frontend/src/app/shared/components/auth/password-reset/finish/password-reset-finish.component.ts @@ -3,6 +3,7 @@ import {ActivatedRoute, Router} from '@angular/router'; import {NgbModalRef} from '@ng-bootstrap/ng-bootstrap'; import {ModalService} from '../../../../../core/modal/modal.service'; import {PasswordResetFinishService} from './password-reset-finish.service'; +import {HttpResponse} from "@angular/common/http"; @Component({ @@ -61,9 +62,10 @@ export class PasswordResetFinishComponent implements OnInit, AfterViewInit { this.success = 'OK'; this.sending = false; }, - () => { + (error: HttpResponse) => { + this.error = error.headers.get('x-utmstack-error') || + 'An internal error has occurred, please try again.'; this.success = null; - this.error = 'ERROR'; this.sending = false; } ); From a04c7104532298107160f4857ec30b2d1c555e84 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 9 Mar 2026 11:26:44 -0500 Subject: [PATCH 185/240] chore: fix conflicts --- agent/agent/logprocessor.go | 48 ++++++++----------------------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/agent/agent/logprocessor.go b/agent/agent/logprocessor.go index 1d392b019..b67fff502 100644 --- a/agent/agent/logprocessor.go +++ b/agent/agent/logprocessor.go @@ -11,23 +11,11 @@ import ( "github.com/google/uuid" "github.com/threatwinds/go-sdk/plugins" -<<<<<<<< HEAD:as400/logservice/processor.go - "github.com/utmstack/UTMStack/as400/agent" - "github.com/utmstack/UTMStack/as400/config" - "github.com/utmstack/UTMStack/as400/conn" - "github.com/utmstack/UTMStack/as400/database" - "github.com/utmstack/UTMStack/as400/models" - "github.com/utmstack/UTMStack/as400/utils" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -======== "github.com/utmstack/UTMStack/agent/config" "github.com/utmstack/UTMStack/agent/database" "github.com/utmstack/UTMStack/agent/models" "github.com/utmstack/UTMStack/agent/utils" "github.com/utmstack/UTMStack/shared/fs" ->>>>>>>> origin/v11:agent/agent/logprocessor.go ) type LogProcessor struct { @@ -90,9 +78,6 @@ func (l *LogProcessor) ProcessLogs(cnf *config.Config, ctx context.Context) { } client := plugins.NewIntegrationClient(connection) -<<<<<<<< HEAD:as400/logservice/processor.go - plClient := createClient(client, ctx, cnf) -======== plClient, err := createClient(client, ctx) if err != nil { if errors.Is(err, ErrAgentUninstalled) { @@ -106,7 +91,6 @@ func (l *LogProcessor) ProcessLogs(cnf *config.Config, ctx context.Context) { utils.Logger.ErrorF("error creating client: %v", err) continue } ->>>>>>>> origin/v11:agent/agent/logprocessor.go l.connErrWritten = false // Create context only after successful client creation to avoid leaks @@ -183,8 +167,16 @@ func (l *LogProcessor) CleanCountedLogs() { for range ticker.C { dataRetention, err := GetDataRetention() if err != nil { - utils.Logger.ErrorF("error getting data retention: %s", err) - continue + utils.Logger.ErrorF("error getting data retention: %s, creating default retention file", err) + if err := SetDataRetention(""); err != nil { + utils.Logger.ErrorF("error creating default data retention: %s", err) + continue + } + dataRetention, err = GetDataRetention() + if err != nil { + utils.Logger.ErrorF("error reading newly created data retention: %s", err) + continue + } } _, err = l.db.DeleteOld(&models.Log{}, dataRetention) if err != nil { @@ -212,32 +204,13 @@ func (l *LogProcessor) CleanCountedLogs() { } } -<<<<<<<< HEAD:as400/logservice/processor.go -func createClient(client plugins.IntegrationClient, ctx context.Context, cnf *config.Config) plugins.Integration_ProcessLogClient { -======== func createClient(client plugins.IntegrationClient, ctx context.Context) (plugins.Integration_ProcessLogClient, error) { ->>>>>>>> origin/v11:agent/agent/logprocessor.go var connErrMsgWritten bool invalidKeyCounter := 0 invalidKeyDelay := timeToSleep maxInvalidKeyDelay := 5 * time.Minute maxInvalidKeyAttempts := 100 // ~8+ hours with backoff before uninstall for { -<<<<<<<< HEAD:as400/logservice/processor.go - authCtx := metadata.AppendToOutgoingContext(ctx, - "key", cnf.CollectorKey, - "id", strconv.Itoa(int(cnf.CollectorID)), - "type", "collector") - - plClient, err := client.ProcessLog(authCtx) - if err != nil { - if strings.Contains(err.Error(), "invalid agent key") { - invalidKeyCounter++ - if invalidKeyCounter >= 20 { - utils.Logger.Info("Uninstalling collector: reason: collector has been removed from the panel...") - _ = agent.UninstallAll() - os.Exit(1) -======== select { case <-ctx.Done(): return nil, ctx.Err() @@ -253,7 +226,6 @@ func createClient(client plugins.IntegrationClient, ctx context.Context) (plugin utils.Logger.ErrorF("uninstalling agent after %d consecutive invalid key errors", maxInvalidKeyAttempts) _ = UninstallAll() return nil, ErrAgentUninstalled ->>>>>>>> origin/v11:agent/agent/logprocessor.go } time.Sleep(invalidKeyDelay) invalidKeyDelay = utils.IncrementReconnectDelay(invalidKeyDelay, maxInvalidKeyDelay) From 18df2bf294bb3be6330bb13b513e86a7bcd8b0e1 Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Mon, 9 Mar 2026 13:44:05 -0400 Subject: [PATCH 186/240] feat: update UTMStack deployment pipeline to build and upload AS400 collectors --- .github/workflows/v11-deployment-pipeline.yml | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/.github/workflows/v11-deployment-pipeline.yml b/.github/workflows/v11-deployment-pipeline.yml index 538de44a3..a8da597bb 100644 --- a/.github/workflows/v11-deployment-pipeline.yml +++ b/.github/workflows/v11-deployment-pipeline.yml @@ -310,18 +310,29 @@ jobs: - name: Check out code into the right branch uses: actions/checkout@v4 - - name: Build UTMStack Collector + - name: Build UTMStack Collectors run: | echo "Building UTMStack Collector..." cd ${{ github.workspace }}/utmstack-collector GOOS=linux GOARCH=amd64 go build -o utmstack_collector -v -ldflags "-X 'github.com/utmstack/UTMStack/utmstack-collector/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . + echo "Building UTMStack AS400 Collector..." + + cd ${{ github.workspace }}/as400 + GOOS=linux GOARCH=amd64 go build -o utmstack_as400_collector_service -v -ldflags "-X 'github.com/utmstack/UTMStack/as400/config.REPLACE_KEY=${{ secrets.AGENT_SECRET_PREFIX }}'" . + + cd ${{ github.workspace }}/as400/updater + GOOS=linux GOARCH=amd64 go build -o utmstack_as400_updater_service -v . + - name: Upload collector binary as artifact uses: actions/upload-artifact@v4 with: - name: utmstack-collector - path: ${{ github.workspace }}/utmstack-collector/utmstack_collector + name: utmstack-collectors + path: | + ${{ github.workspace }}/utmstack-collector/utmstack_collector + ${{ github.workspace }}/as400/utmstack_as400_collector_service + ${{ github.workspace }}/as400/updater/utmstack_as400_updater_service retention-days: 1 build_agent_manager: @@ -339,10 +350,10 @@ jobs: name: signed-agents path: ${{ github.workspace }}/agent - - name: Download UTMStack Collector from artifacts + - name: Download UTMStack Collectors from artifacts uses: actions/download-artifact@v4 with: - name: utmstack-collector + name: utmstack-collectors path: ${{ github.workspace }}/utmstack-collector - name: Download signed macOS agents from artifacts @@ -357,12 +368,16 @@ jobs: GOOS=linux GOARCH=amd64 go build -o agent-manager -v . mkdir -p ./dependencies/collector - curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/collector/linux-as400-collector.zip" -o ./dependencies/collector/linux-as400-collector.zip - curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/collector/windows-as400-collector.zip" -o ./dependencies/collector/windows-as400-collector.zip - - cp "${{ github.workspace }}/utmstack-collector/utmstack_collector" ./dependencies/collector/ + cp "${{ github.workspace }}/utmstack-collector/utmstack-collector/utmstack_collector" ./dependencies/collector/ cp "${{ github.workspace }}/utmstack-collector/version.json" ./dependencies/collector/ + mkdir -p ./dependencies/collector/as400 + curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/collector/as400-collector.jar" -o ./dependencies/collector/as400-collector.jar + + cp "${{ github.workspace }}/as400/version.json" ./dependencies/collector/as400/ + cp "${{ github.workspace }}/utmstack-collector/as400/utmstack_as400_collector_service" ./dependencies/collector/as400/ + cp "${{ github.workspace }}/utmstack-collector/as400/updater/utmstack_as400_updater_service" ./dependencies/collector/as400/ + mkdir -p ./dependencies/agent/ # Linux agents @@ -405,6 +420,7 @@ jobs: with: context: ./agent-manager push: true + provenance: false tags: ghcr.io/utmstack/utmstack/agent-manager:${{ needs.setup_deployment.outputs.tag }} build_event_processor: @@ -460,6 +476,7 @@ jobs: context: . file: ./event_processor.Dockerfile push: true + provenance: false tags: ghcr.io/utmstack/utmstack/eventprocessor:${{ needs.setup_deployment.outputs.tag }} build-args: | BASE_IMAGE=ghcr.io/threatwinds/eventprocessor/base:${{ needs.setup_deployment.outputs.event_processor_tag }} From 5c425cbaa0bf853183bd9a5429d1bba4e9ba551d Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Mon, 9 Mar 2026 13:49:11 -0400 Subject: [PATCH 187/240] refactor(as400): reorganize logservice package and improve log processing logic --- as400/logservice/processor.go | 175 ++++++++++++++-------------------- 1 file changed, 71 insertions(+), 104 deletions(-) diff --git a/as400/logservice/processor.go b/as400/logservice/processor.go index 1d392b019..ac7799c75 100644 --- a/as400/logservice/processor.go +++ b/as400/logservice/processor.go @@ -1,8 +1,9 @@ -package agent +package logservice import ( "context" "errors" + "os" "strconv" "strings" "sync" @@ -11,7 +12,6 @@ import ( "github.com/google/uuid" "github.com/threatwinds/go-sdk/plugins" -<<<<<<<< HEAD:as400/logservice/processor.go "github.com/utmstack/UTMStack/as400/agent" "github.com/utmstack/UTMStack/as400/config" "github.com/utmstack/UTMStack/as400/conn" @@ -21,13 +21,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" -======== - "github.com/utmstack/UTMStack/agent/config" - "github.com/utmstack/UTMStack/agent/database" - "github.com/utmstack/UTMStack/agent/models" - "github.com/utmstack/UTMStack/agent/utils" - "github.com/utmstack/UTMStack/shared/fs" ->>>>>>>> origin/v11:agent/agent/logprocessor.go ) type LogProcessor struct { @@ -38,48 +31,31 @@ type LogProcessor struct { } var ( - processor LogProcessor - processorOnce sync.Once - processorInitErr error - LogQueue = make(chan *plugins.Log, 10000) - timeCLeanLogs = 10 * time.Minute - - // ErrAgentUninstalled is returned when the agent uninstalls itself due to invalid key - ErrAgentUninstalled = errors.New("agent uninstalled due to invalid key") + processor LogProcessor + processorOnce sync.Once + LogQueue = make(chan *plugins.Log) + timeToSleep = 10 * time.Second + timeCLeanLogs = 10 * time.Minute ) -func GetLogProcessor() (*LogProcessor, error) { +func GetLogProcessor() LogProcessor { processorOnce.Do(func() { - db, err := database.GetDB() - if err != nil { - processorInitErr = err - return - } processor = LogProcessor{ - db: db, + db: database.GetDB(), connErrWritten: false, ackErrWritten: false, sendErrWritten: false, } }) - if processorInitErr != nil { - return nil, processorInitErr - } - return &processor, nil + return processor } func (l *LogProcessor) ProcessLogs(cnf *config.Config, ctx context.Context) { go l.CleanCountedLogs() for { - select { - case <-ctx.Done(): - utils.Logger.Info("ProcessLogs stopping due to context cancellation") - return - default: - } - - connection, err := GetCorrelationConnection(cnf) + ctxEof, cancelEof := context.WithCancel(context.Background()) + connection, err := conn.GetCorrelationConnection(cnf) if err != nil { if !l.connErrWritten { utils.Logger.ErrorF("error connecting to Correlation: %v", err) @@ -90,27 +66,9 @@ func (l *LogProcessor) ProcessLogs(cnf *config.Config, ctx context.Context) { } client := plugins.NewIntegrationClient(connection) -<<<<<<<< HEAD:as400/logservice/processor.go plClient := createClient(client, ctx, cnf) -======== - plClient, err := createClient(client, ctx) - if err != nil { - if errors.Is(err, ErrAgentUninstalled) { - utils.Logger.Info("Agent uninstalled, stopping log processor") - return - } - if errors.Is(err, context.Canceled) { - utils.Logger.Info("ProcessLogs stopping due to context cancellation") - return - } - utils.Logger.ErrorF("error creating client: %v", err) - continue - } ->>>>>>>> origin/v11:agent/agent/logprocessor.go l.connErrWritten = false - // Create context only after successful client creation to avoid leaks - ctxEof, cancelEof := context.WithCancel(context.Background()) go l.handleAcknowledgements(plClient, ctxEof, cancelEof) l.processLogs(plClient, ctxEof, cancelEof) } @@ -124,20 +82,38 @@ func (l *LogProcessor) handleAcknowledgements(plClient plugins.Integration_Proce default: ack, err := plClient.Recv() if err != nil { - action := HandleGRPCStreamError(err, "failed to receive ack", &l.ackErrWritten) - if action == ActionReconnect { + if strings.Contains(err.Error(), "EOF") { + time.Sleep(timeToSleep) cancel() return } - continue + st, ok := status.FromError(err) + if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { + if !l.ackErrWritten { + utils.Logger.ErrorF("failed to receive ack: %v", err) + l.ackErrWritten = true + } + time.Sleep(timeToSleep) + cancel() + return + } else { + if !l.ackErrWritten { + utils.Logger.ErrorF("failed to receive ack: %v", err) + l.ackErrWritten = true + } + time.Sleep(timeToSleep) + continue + } } l.ackErrWritten = false + l.db.Lock() err = l.db.Update(&models.Log{}, "id", ack.LastId, "processed", true) if err != nil { utils.Logger.ErrorF("failed to update log: %v", err) } + l.db.Unlock() } } } @@ -149,28 +125,44 @@ func (l *LogProcessor) processLogs(plClient plugins.Integration_ProcessLogClient utils.Logger.Info("context done, exiting processLogs") return case newLog := <-LogQueue: - if newLog.Id == "" { - id, err := uuid.NewRandom() - if err != nil { - utils.Logger.ErrorF("failed to generate uuid: %v", err) - continue - } + id, err := uuid.NewRandom() + if err != nil { + utils.Logger.ErrorF("failed to generate uuid: %v", err) + continue + } - newLog.Id = id.String() - err = l.db.Create(&models.Log{ID: newLog.Id, Log: newLog.Raw, Type: newLog.DataType, CreatedAt: time.Now(), DataSource: newLog.DataSource, Processed: false}) - if err != nil { - utils.Logger.ErrorF("failed to save log: %v :log: %s", err, newLog.Raw) - } + newLog.Id = id.String() + l.db.Lock() + err = l.db.Create(&models.Log{ID: newLog.Id, Log: newLog.Raw, Type: newLog.DataType, CreatedAt: time.Now(), DataSource: newLog.DataSource, Processed: false}) + if err != nil { + utils.Logger.ErrorF("failed to save log: %v :log: %s", err, newLog.Raw) } + l.db.Unlock() - err := plClient.Send(newLog) + err = plClient.Send(newLog) if err != nil { - action := HandleGRPCStreamError(err, "failed to send log", &l.sendErrWritten) - if action == ActionReconnect { + if strings.Contains(err.Error(), "EOF") { + time.Sleep(timeToSleep) cancel() return } - continue + st, ok := status.FromError(err) + if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) { + if !l.sendErrWritten { + utils.Logger.ErrorF("failed to send log: %v :log: %s", err, newLog.Raw) + l.sendErrWritten = true + } + time.Sleep(timeToSleep) + cancel() + return + } else { + if !l.sendErrWritten { + utils.Logger.ErrorF("failed to send log: %v :log: %s", err, newLog.Raw) + l.sendErrWritten = true + } + time.Sleep(timeToSleep) + continue + } } l.sendErrWritten = false } @@ -186,13 +178,17 @@ func (l *LogProcessor) CleanCountedLogs() { utils.Logger.ErrorF("error getting data retention: %s", err) continue } + l.db.Lock() _, err = l.db.DeleteOld(&models.Log{}, dataRetention) if err != nil { utils.Logger.ErrorF("error deleting old logs: %s", err) } + l.db.Unlock() unprocessed := make([]models.Log, 0, 10) + l.db.Lock() found, err := l.db.Find(&unprocessed, "processed", false) + l.db.Unlock() if err != nil { utils.Logger.ErrorF("error finding unprocessed logs: %s", err) continue @@ -212,18 +208,10 @@ func (l *LogProcessor) CleanCountedLogs() { } } -<<<<<<<< HEAD:as400/logservice/processor.go func createClient(client plugins.IntegrationClient, ctx context.Context, cnf *config.Config) plugins.Integration_ProcessLogClient { -======== -func createClient(client plugins.IntegrationClient, ctx context.Context) (plugins.Integration_ProcessLogClient, error) { ->>>>>>>> origin/v11:agent/agent/logprocessor.go var connErrMsgWritten bool invalidKeyCounter := 0 - invalidKeyDelay := timeToSleep - maxInvalidKeyDelay := 5 * time.Minute - maxInvalidKeyAttempts := 100 // ~8+ hours with backoff before uninstall for { -<<<<<<<< HEAD:as400/logservice/processor.go authCtx := metadata.AppendToOutgoingContext(ctx, "key", cnf.CollectorKey, "id", strconv.Itoa(int(cnf.CollectorID)), @@ -237,30 +225,9 @@ func createClient(client plugins.IntegrationClient, ctx context.Context) (plugin utils.Logger.Info("Uninstalling collector: reason: collector has been removed from the panel...") _ = agent.UninstallAll() os.Exit(1) -======== - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - - plClient, err := client.ProcessLog(ctx) - if err != nil { - if strings.Contains(err.Error(), "invalid agent key") { - invalidKeyCounter++ - utils.Logger.ErrorF("invalid agent key (attempt %d/%d), retrying in %v", invalidKeyCounter, maxInvalidKeyAttempts, invalidKeyDelay) - if invalidKeyCounter >= maxInvalidKeyAttempts { - utils.Logger.ErrorF("uninstalling agent after %d consecutive invalid key errors", maxInvalidKeyAttempts) - _ = UninstallAll() - return nil, ErrAgentUninstalled ->>>>>>>> origin/v11:agent/agent/logprocessor.go } - time.Sleep(invalidKeyDelay) - invalidKeyDelay = utils.IncrementReconnectDelay(invalidKeyDelay, maxInvalidKeyDelay) - continue } else { invalidKeyCounter = 0 - invalidKeyDelay = timeToSleep } if !connErrMsgWritten { utils.Logger.ErrorF("failed to create input client: %v", err) @@ -269,7 +236,7 @@ func createClient(client plugins.IntegrationClient, ctx context.Context) (plugin time.Sleep(timeToSleep) continue } - return plClient, nil + return plClient } } @@ -287,12 +254,12 @@ func SetDataRetention(retention string) error { return errors.New("retention must be greater than 0") } - return fs.WriteJSON(config.RetentionConfigFile, models.DataRetention{Retention: retentionInt}) + return utils.WriteJSON(config.RetentionConfigFile, models.DataRetention{Retention: retentionInt}) } func GetDataRetention() (int, error) { retention := models.DataRetention{} - err := fs.ReadJSON(config.RetentionConfigFile, &retention) + err := utils.ReadJson(config.RetentionConfigFile, &retention) if err != nil { return 0, err } From 8e773a5b6f54feb975128e1a054f17607032dbee Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Mon, 9 Mar 2026 14:13:21 -0400 Subject: [PATCH 188/240] fix: update path for AS400 collector JAR in deployment pipeline --- .github/workflows/v11-deployment-pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/v11-deployment-pipeline.yml b/.github/workflows/v11-deployment-pipeline.yml index a8da597bb..93cb8d925 100644 --- a/.github/workflows/v11-deployment-pipeline.yml +++ b/.github/workflows/v11-deployment-pipeline.yml @@ -372,7 +372,7 @@ jobs: cp "${{ github.workspace }}/utmstack-collector/version.json" ./dependencies/collector/ mkdir -p ./dependencies/collector/as400 - curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/collector/as400-collector.jar" -o ./dependencies/collector/as400-collector.jar + curl -sSL "https://storage.googleapis.com/utmstack-updates/dependencies/collector/as400-collector.jar" -o ./dependencies/collector/as400/as400-collector.jar cp "${{ github.workspace }}/as400/version.json" ./dependencies/collector/as400/ cp "${{ github.workspace }}/utmstack-collector/as400/utmstack_as400_collector_service" ./dependencies/collector/as400/ From fdc9be29359fd30dbcf84898e384781a91a62df0 Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Mon, 9 Mar 2026 14:51:19 -0400 Subject: [PATCH 189/240] fix: correct updater service name in DownloadUpdater function --- as400/updates/dependencies.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/as400/updates/dependencies.go b/as400/updates/dependencies.go index f2db5937c..58ec14490 100644 --- a/as400/updates/dependencies.go +++ b/as400/updates/dependencies.go @@ -17,8 +17,8 @@ func DownloadVersion(address string, insecure bool) error { } func DownloadUpdater(address string, insecure bool) error { - if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, address, config.DependenciesPort, "utmstack_updater_service"), map[string]string{}, "utmstack_updater_service", utils.GetMyPath(), insecure); err != nil { - return fmt.Errorf("error downloading utmstack_updater_service : %v", err) + if err := utils.DownloadFile(fmt.Sprintf(config.DependUrl, address, config.DependenciesPort, "utmstack_as400_updater_service"), map[string]string{}, "utmstack_as400_updater_service", utils.GetMyPath(), insecure); err != nil { + return fmt.Errorf("error downloading utmstack_as400_updater_service : %v", err) } return nil From 0d4d111354be009179eb4ebe09f78d18f4bc4d62 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 9 Mar 2026 14:17:08 -0500 Subject: [PATCH 190/240] feat: update AS400 installation instructions and add pre-installation requirements Signed-off-by: Manuel Abascal --- .../guides/guide-as400/constants.ts | 28 ++++++++----------- .../guide-as400/guide-as400.component.html | 18 +++++++++++- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/frontend/src/app/app-module/guides/guide-as400/constants.ts b/frontend/src/app/app-module/guides/guide-as400/constants.ts index 45663c52f..6b126ebdc 100644 --- a/frontend/src/app/app-module/guides/guide-as400/constants.ts +++ b/frontend/src/app/app-module/guides/guide-as400/constants.ts @@ -1,6 +1,6 @@ export const PLATFORM = [ - { + /*{ id: 1, name: 'WINDOWS', install: `New-Item -ItemType Directory -Force -Path "C:\\Program Files\\UTMStack\\UTMStack Collectors\\AS400"; ` + @@ -24,25 +24,19 @@ export const PLATFORM = [ `-Recurse -Force -ErrorAction Stop; Write-Host "UTMStack AS400 Collector removed successfully."`, shell: 'Open Windows Powershell terminal as “ADMINISTRATOR”' - }, - { - id: 2, - name: 'LINUX UBUNTU', - install: `sudo bash -c "apt update -y && apt install wget unzip -y && mkdir -p ` + - `/opt/utmstack-linux-collectors/as400 && cd /opt/utmstack-linux-collectors/as400 && ` + - `wget --no-check-certificate ` + - `https://V_IP:9001/private/dependencies/collector/linux-as400-collector.zip ` + - `&& unzip linux-as400-collector.zip && rm linux-as400-collector.zip && chmod -R 755 ` + - `utmstack_collectors_installer && ./utmstack_collectors_installer install as400 ` + - `V_IP V_TOKEN"`, + },*/ + { + id: 2, + name: 'LINUX UBUNTU', + // tslint:disable-next-line:max-line-length + install: `sudo bash -c "apt update -y && apt install wget -y && mkdir -p /opt/utmstack-as400-collector && wget --no-check-certificate -P /opt/utmstack-as400-collector https://V_IP:9001/private/dependencies/collector/as400/utmstack_as400_collector_service && chmod -R 755 /opt/utmstack-as400-collector/utmstack_as400_collector_service && /opt/utmstack-as400-collector/utmstack_as400_collector_service install V_IP V_TOKEN yes"`, + // tslint:disable-next-line:max-line-length + uninstall: `sudo bash -c " cd /opt/utmstack-linux-collectors/as400 && ./utmstack_collectors_installer uninstall as400 && echo 'Removing UTMStack AS400 Collector dependencies...' && sleep 5 && rm -rf /opt/utmstack-linux-collectors/as400 && echo 'UTMStack AS400 Collector removed successfully.'"`, - uninstall: `sudo bash -c " cd /opt/utmstack-linux-collectors/as400 && ./utmstack_collectors_installer ` + - `uninstall as400 && echo 'Removing UTMStack AS400 Collector dependencies...' && sleep 5 && rm ` + - `-rf /opt/utmstack-linux-collectors/as400 && echo 'UTMStack AS400 Collector removed successfully.'"`, + shell: 'Linux bash terminal' + } - shell: 'Linux bash terminal' - } ]; export const ACTIONS = [ diff --git a/frontend/src/app/app-module/guides/guide-as400/guide-as400.component.html b/frontend/src/app/app-module/guides/guide-as400/guide-as400.component.html index b8aebf00e..85e8266f3 100644 --- a/frontend/src/app/app-module/guides/guide-as400/guide-as400.component.html +++ b/frontend/src/app/app-module/guides/guide-as400/guide-as400.component.html @@ -10,7 +10,23 @@

The UTMStack AS400 Collector communicate over ports 9000, 9001 and 50051. Please make sure these ports are open.

    - +
  1. +

    + 1 + Check pre-installation requirements +

    +
      +
    • Compatible with IBM i (AS400) systems that allow remote access to system logs.
    • +
    • The AS400 Collector requires access to IBM i Host Server services. Make sure the following ports are open from the collector host to the AS400:
    • +
        +
      • 8476 – Signon Server (authentication)
      • +
      • 446 – DRDA/JDBC (SQL-based log access, when available)
      • +
      • 8471, 8470, 8475, 8472 – Required when SQL log access is not available and the collector uses the classic JT400 method
      • +
      +
    +
  2. + +
  3. From a0898b408e155613fb2c6c7ace2b481d37102530 Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Mon, 9 Mar 2026 15:41:09 -0400 Subject: [PATCH 191/240] fix(as400): update service path to include 'as400' in InstallUpdater and UninstallUpdater functions --- as400/utils/updater.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/as400/utils/updater.go b/as400/utils/updater.go index 0083171ee..6c2543d7b 100644 --- a/as400/utils/updater.go +++ b/as400/utils/updater.go @@ -3,7 +3,7 @@ package utils import "fmt" func InstallUpdater() error { - updaterPath := GetMyPath() + "/utmstack_updater_service" + updaterPath := GetMyPath() + "/utmstack_as400_updater_service" if err := Execute("chmod", GetMyPath(), "+x", updaterPath); err != nil { return fmt.Errorf("error setting execute permissions: %v", err) @@ -17,7 +17,7 @@ func InstallUpdater() error { } func UninstallUpdater() error { - updaterPath := GetMyPath() + "/utmstack_updater_service" + updaterPath := GetMyPath() + "/utmstack_as400_updater_service" if !CheckIfPathExist(updaterPath) { return nil From 4edc292b1b1cc70122fe0ad7d43bcc9904cd9302 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 9 Mar 2026 14:48:40 -0500 Subject: [PATCH 192/240] feat: add SAML2 proxy configuration for authentication --- installer/templates/front-end.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/installer/templates/front-end.go b/installer/templates/front-end.go index 76d48cc52..dc960dcd9 100644 --- a/installer/templates/front-end.go +++ b/installer/templates/front-end.go @@ -15,10 +15,25 @@ const FrontEnd string = `server { set $utmstack_agent_manager http://agentmanager:9001; set $utmstack_backend_auth http://backend:8080/api/authenticate; set $utmstack_ws http://backend:8080/ws; - set $utmstack_saml2 http://backend:8080/login/saml2/; set $shared_key {{.SharedKey}}; set $shared_key_header $http_x_shared_key; + location /saml2/ { + proxy_pass http://backend:8080/saml2/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /login/saml2/ { + proxy_pass http://backend:8080/login/saml2/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location /api { proxy_pass $utmstack_backend; proxy_set_header Host $host; From a8b094af97602d6d84fe3141d793ec3a78a5bf35 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Mon, 9 Mar 2026 16:18:07 -0500 Subject: [PATCH 193/240] fix: improve installation and uninstallation scripts for AS400 collector Signed-off-by: Manuel Abascal --- .../guides/guide-as400/constants.ts | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/app-module/guides/guide-as400/constants.ts b/frontend/src/app/app-module/guides/guide-as400/constants.ts index 6b126ebdc..1086b9fc5 100644 --- a/frontend/src/app/app-module/guides/guide-as400/constants.ts +++ b/frontend/src/app/app-module/guides/guide-as400/constants.ts @@ -28,11 +28,25 @@ export const PLATFORM = [ { id: 2, name: 'LINUX UBUNTU', - // tslint:disable-next-line:max-line-length - install: `sudo bash -c "apt update -y && apt install wget -y && mkdir -p /opt/utmstack-as400-collector && wget --no-check-certificate -P /opt/utmstack-as400-collector https://V_IP:9001/private/dependencies/collector/as400/utmstack_as400_collector_service && chmod -R 755 /opt/utmstack-as400-collector/utmstack_as400_collector_service && /opt/utmstack-as400-collector/utmstack_as400_collector_service install V_IP V_TOKEN yes"`, - // tslint:disable-next-line:max-line-length - uninstall: `sudo bash -c " cd /opt/utmstack-linux-collectors/as400 && ./utmstack_collectors_installer uninstall as400 && echo 'Removing UTMStack AS400 Collector dependencies...' && sleep 5 && rm -rf /opt/utmstack-linux-collectors/as400 && echo 'UTMStack AS400 Collector removed successfully.'"`, + install: `sudo bash -c " + apt update -y && \ + apt install wget -y && \ + mkdir -p /opt/utmstack-as400-collector && \ + wget --no-check-certificate -P /opt/utmstack-as400-collector https://V_IP:9001/private/dependencies/collector/as400/utmstack_as400_collector_service && \ + chmod -R 755 /opt/utmstack-as400-collector/utmstack_as400_collector_service && \ + /opt/utmstack-as400-collector/utmstack_as400_collector_service install V_IP V_TOKEN yes + "`, + + uninstall: `sudo bash -c " + /opt/utmstack-as400-collector/utmstack_as400_collector_service uninstall || true; \ + systemctl stop UTMStackAS400Collector 2>/dev/null || true; \ + systemctl disable UTMStackAS400Collector 2>/dev/null || true; \ + rm -f /etc/systemd/system/UTMStackAS400Collector.service 2>/dev/null || true; \ + echo 'Removing UTMStack AS400 dependencies...' && sleep 10; \ + rm -rf /opt/utmstack-as400-collector 2>/dev/null || true; \ + echo 'UTMStack AS400 dependencies removed successfully.' + "`, shell: 'Linux bash terminal' } From d546254a40c23baa9c254f281826e658f3dd10d6 Mon Sep 17 00:00:00 2001 From: Yadian Llada Lopez Date: Mon, 9 Mar 2026 17:24:30 -0400 Subject: [PATCH 194/240] fix(as400): correct REPLACE_KEY declaration - Change REPLACE_KEY from const to var to allow -ldflags -X injection --- as400/config/const.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/as400/config/const.go b/as400/config/const.go index db4762522..f46f67881 100644 --- a/as400/config/const.go +++ b/as400/config/const.go @@ -6,9 +6,10 @@ import ( "github.com/utmstack/UTMStack/as400/utils" ) +var REPLACE_KEY string = "" + const ( - REPLACE_KEY string = "" - DataType string = "ibm-as400" + DataType string = "ibm-as400" ) var ( From 57acab7258fe61f9b6b24b8134b46468458edfb7 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 10 Mar 2026 10:02:19 -0500 Subject: [PATCH 195/240] fix: handle null return case in saveCollector method Signed-off-by: Manuel Abascal --- .../int-config-types/collector-configuration.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/app/app-module/conf/int-generic-group-config/int-config-types/collector-configuration.ts b/frontend/src/app/app-module/conf/int-generic-group-config/int-config-types/collector-configuration.ts index d0979bdd6..dc564b951 100644 --- a/frontend/src/app/app-module/conf/int-generic-group-config/int-config-types/collector-configuration.ts +++ b/frontend/src/app/app-module/conf/int-generic-group-config/int-config-types/collector-configuration.ts @@ -66,6 +66,8 @@ export class CollectorConfiguration extends IntegrationConfig { groups: collector.groups.filter(g => g.id !== group.id) }; return this.saveCollector(group); + } else { + return of(null); } } From 59fa55cdd347c301fda40c57bf60cd7ca028ca49 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 10 Mar 2026 11:35:18 -0500 Subject: [PATCH 196/240] refactor: update FileFieldEnum values for consistency with new logging format Signed-off-by: Manuel Abascal --- .../shared/enum/file-field.enum.ts | 72 ++++++++----------- 1 file changed, 31 insertions(+), 41 deletions(-) diff --git a/frontend/src/app/data-management/file-management/shared/enum/file-field.enum.ts b/frontend/src/app/data-management/file-management/shared/enum/file-field.enum.ts index 364e076e4..46f200684 100644 --- a/frontend/src/app/data-management/file-management/shared/enum/file-field.enum.ts +++ b/frontend/src/app/data-management/file-management/shared/enum/file-field.enum.ts @@ -1,44 +1,34 @@ export enum FileFieldEnum { FILE_TIMESTAMP_FIELD = '@timestamp', - FILE_VERSION_FIELD = '@version', - FILE_ID_FIELD = '_id', - FILE_BEAT_HOSTNAME_FIELD = 'logx.wineventlog.beat.hostname', - FILE_BEAT_NAME_FIELD = 'logx.wineventlog.beat.name', - FILE_BEAT_VERSION_FIELD = 'logx.wineventlog.beat.version', - FILE_COMPUTER_NAME_FIELD = 'logx.wineventlog.computer_name', - FILE_ACCESS_LIST_FIELD = 'logx.wineventlog.event_data.AccessList', - FILE_ACCESS_MASK_FIELD = 'logx.wineventlog.event_data.AccessMask', - FILE_HANDLE_ID_FIELD = 'logx.wineventlog.event_data.HandleId', - FILE_OBJECT_NAME_FIELD = 'logx.wineventlog.event_data.ObjectName', - FILE_OBJECT_SERVER_FIELD = 'logx.wineventlog.event_data.ObjectServer', - FILE_OBJECT_TYPE_FIELD = 'logx.wineventlog.event_data.ObjectType', - FILE_PROCESS_ID_FIELD = 'logx.wineventlog.event_data.ProcessId', - FILE_PROCESS_NAME_FIELD = 'logx.wineventlog.event_data.ProcessName', - FILE_RESOURCE_ATT_FIELD = 'logx.wineventlog.event_data.ResourceAttributes', - FILE_SUBJECT_DOMAIN_NAME_FIELD = 'logx.wineventlog.event_data.SubjectDomainName', - FILE_SUBJECT_LOGON_ID_FIELD = 'logx.wineventlog.event_data.SubjectLogonId', - FILE_SUBJECT_USER_NAME_FIELD = 'logx.wineventlog.event_data.SubjectUserName', - FILE_SUBJECT_USER_ID_FIELD = 'logx.wineventlog.event_data.SubjectUserSid', - FILE_EVENT_ID_FIELD = 'logx.wineventlog.event_id', - FILE_EVENT_NAME_FIELD = 'logx.wineventlog.event_name', - FILE_HOST_ARCHITECTURE_FIELD = 'logx.wineventlog.host.architecture', - FILE_HOST_ID_FIELD = 'logx.wineventlog.host.id', - FILE_HOST_NAME_FIELD = 'logx.wineventlog.host.name', - FILE_HOST_OS_NAME_FIELD = 'logx.wineventlog.host.os.name', - FILE_MESSAGE_FIELD = 'logx.wineventlog.message', - FILE_NEW_SDDL_FIELD = 'logx.wineventlog.event_data.NewSd', - FILE_OLD_SDDL_FIELD = 'logx.wineventlog.event_data.OldSd', - FILE_HOTS_OS_BUILD_FIELD = 'logx.wineventlog.host.os.build', - FILE_HOST_OS_FAMILY_FIELD = 'logx.wineventlog.host.os.family', - FILE_HOST_OS_PLATFORM_FIELD = 'logx.wineventlog.host.os.platform', - FILE_HOST_OS_VERSION_FIELD = 'logx.wineventlog.host.os.version', - FILE_KEYWORD_FIELD = 'logx.wineventlog.keywords', - FILE_LEVEL_FIELD = 'logx.wineventlog.level', - FILE_LOG_NAME_FIELD = 'logx.wineventlog.log_name', - FILE_OPCODE_FIELD = 'logx.wineventlog.opcode', - FILE_PROCESS_ID_SECONDARY_FIELD = 'logx.wineventlog.process_id', - FILE_PROVIDER_GUID_FIELD = 'logx.wineventlog.provider_guid', - FILE_SHARE_NAME_FIELD = 'logx.wineventlog.event_data.ShareName', - FILE_SHARE_PATH_FIELD = 'logx.wineventlog.event_data.ShareLocalPath', - FILE_SHARE_IPPORT_FIELD = 'logx.wineventlog.event_data.IpPort', + FILE_ACCESS_LIST_FIELD = 'log.eventDataAccessList', + FILE_ACCESS_MASK_FIELD = 'log.eventDataAccessMask', + FILE_HANDLE_ID_FIELD = 'log.eventDataHandleId', + FILE_OBJECT_NAME_FIELD = 'log.eventDataObjectName', + FILE_OBJECT_SERVER_FIELD = 'log.eventDataObjectServer', + FILE_OBJECT_TYPE_FIELD = 'log.eventDataObjectType', + FILE_PROCESS_ID_FIELD = 'log.eventDataProcessId', + FILE_PROCESS_NAME_FIELD = 'log.eventDataProcessName', + FILE_RESOURCE_ATT_FIELD = 'log.eventDataResourceAttributes', + FILE_SUBJECT_DOMAIN_NAME_FIELD = 'log.eventDataSubjectDomainName', + FILE_SUBJECT_LOGON_ID_FIELD = 'log.eventDataSubjectLogonId', + FILE_SUBJECT_USER_NAME_FIELD = 'log.eventDataSubjectUserName', + FILE_SUBJECT_USER_ID_FIELD = 'log.eventDataSubjectUserSid', + FILE_EVENT_ID_FIELD = 'log.eventCode', + FILE_EVENT_NAME_FIELD = 'log.eventName', + FILE_HOST_ARCHITECTURE_FIELD = 'log.cpuArchitecture', + FILE_HOST_ID_FIELD = 'log.id', + FILE_HOST_NAME_FIELD = 'origin.host', + FILE_HOST_OS_NAME_FIELD = 'log.computer', + FILE_MESSAGE_FIELD = 'log.eventName', + FILE_NEW_SDDL_FIELD = 'log.eventDataNewSd', + FILE_OLD_SDDL_FIELD = 'log.eventDataOldSd', + FILE_HOTS_OS_BUILD_FIELD = 'log.host.os.build', + FILE_HOST_OS_FAMILY_FIELD = 'log.host.os.family', + FILE_HOST_OS_PLATFORM_FIELD = 'log.host.os.platform', + FILE_HOST_OS_VERSION_FIELD = 'log.host.os.version', + FILE_KEYWORD_FIELD = 'log.keywords', + FILE_OPCODE_FIELD = 'log.opcode', + FILE_PROVIDER_GUID_FIELD = 'log.providerGuid', + FILE_SHARE_NAME_FIELD = 'log.eventDataShareName', + FILE_SHARE_PATH_FIELD = 'log.eventDataShareLocalPath', } From b782ae921afd751f4c6d8f7181eeef1713efdad4 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 10 Mar 2026 11:45:45 -0500 Subject: [PATCH 197/240] feat: enhance module deletion process with event processing and DTO mapping --- .../UtmModuleGroupService.java | 9 +++++++- .../UtmModuleGroupResource.java | 23 ++++++++----------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java index 57eefeda2..4e0ca7b08 100644 --- a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java +++ b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java @@ -6,11 +6,14 @@ import com.park.utmstack.domain.application_modules.UtmModule; import com.park.utmstack.domain.application_modules.UtmModuleGroup; import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; +import com.park.utmstack.event_processor.EventProcessorManagerService; import com.park.utmstack.repository.UtmModuleGroupConfigurationRepository; import com.park.utmstack.repository.UtmModuleGroupRepository; import com.park.utmstack.repository.application_modules.UtmModuleRepository; import com.park.utmstack.service.application_events.ApplicationEventService; import com.park.utmstack.service.dto.application_modules.ModuleActivationDTO; +import com.park.utmstack.service.dto.application_modules.ModuleDTO; +import com.park.utmstack.service.dto.application_modules.UtmModuleMapper; import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.util.CipherUtil; import com.park.utmstack.util.exceptions.ApiException; @@ -45,6 +48,8 @@ public class UtmModuleGroupService { private final ApplicationEventService applicationEventService; private final UtmModuleRepository moduleRepository; private final UtmModuleGroupConfigurationRepository moduleGroupConfigurationRepository; + private final EventProcessorManagerService eventProcessorManagerService; + private final UtmModuleMapper moduleMapper; /** @@ -89,7 +94,7 @@ public Optional findOne(Long id) { * @param id the id of the entity */ - public void delete(Long id) { + public UtmModule delete(Long id) { final String ctx = CLASSNAME + ".delete"; long start = System.currentTimeMillis(); @@ -111,6 +116,8 @@ public void delete(Long id) { long duration = System.currentTimeMillis() - start; String successMsg = String.format("Configuration group (ID: %d) for module '%s' deleted successfully in %dms", id, moduleName, duration); applicationEventService.createEvent(successMsg, ApplicationEventType.CONFIG_GROUP_DELETE_SUCCESS, extra); + + return moduleGroup.getModule(); } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/application_modules/UtmModuleGroupResource.java b/backend/src/main/java/com/park/utmstack/web/rest/application_modules/UtmModuleGroupResource.java index 9f10dbe3c..62b2314d0 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/application_modules/UtmModuleGroupResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/application_modules/UtmModuleGroupResource.java @@ -7,12 +7,16 @@ import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; import com.park.utmstack.domain.application_modules.factory.ModuleFactory; import com.park.utmstack.domain.application_modules.types.ModuleConfigurationKey; +import com.park.utmstack.event_processor.EventProcessorManagerService; import com.park.utmstack.service.application_events.ApplicationEventService; import com.park.utmstack.service.application_modules.UtmModuleGroupConfigurationService; import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.application_modules.UtmModuleService; +import com.park.utmstack.service.dto.application_modules.ModuleDTO; +import com.park.utmstack.service.dto.application_modules.UtmModuleMapper; import com.park.utmstack.web.rest.util.HeaderUtil; import com.park.utmstack.web.rest.vm.ModuleGroupVM; +import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataIntegrityViolationException; @@ -32,6 +36,7 @@ * REST controller for managing UtmConfigurationGroup. */ @RestController +@RequiredArgsConstructor @RequestMapping("/api") public class UtmModuleGroupResource { @@ -45,18 +50,8 @@ public class UtmModuleGroupResource { private final ModuleFactory moduleFactory; private final UtmModuleService moduleService; private final UtmModuleGroupConfigurationService moduleGroupConfigurationService; - - public UtmModuleGroupResource(UtmModuleGroupService moduleGroupService, - ApplicationEventService eventService, - ModuleFactory moduleFactory, - UtmModuleService moduleService, - UtmModuleGroupConfigurationService moduleGroupConfigurationService) { - this.moduleGroupService = moduleGroupService; - this.eventService = eventService; - this.moduleFactory = moduleFactory; - this.moduleService = moduleService; - this.moduleGroupConfigurationService = moduleGroupConfigurationService; - } + private final UtmModuleMapper moduleMapper; + private final EventProcessorManagerService eventProcessorManagerService; @PostMapping("/utm-configuration-groups") @AuditEvent( @@ -165,7 +160,9 @@ public ResponseEntity getConfigurationGroup(@PathVariable Long g public ResponseEntity deleteSingleModuleGroup(@RequestParam Long groupId) { final String ctx = CLASSNAME + ".deleteSingleModuleGroup"; - moduleGroupService.delete(groupId); + UtmModule module = moduleGroupService.delete(groupId); + ModuleDTO moduleDTO = moduleMapper.toDto(module, false); + eventProcessorManagerService.updateModule(moduleDTO); return ResponseEntity.ok().build(); } From 6f2fe2ee28762d5d2e953235fda1230b3513daac Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Tue, 10 Mar 2026 13:34:54 -0500 Subject: [PATCH 198/240] feat: implement deleteAndFetch method for module group deletion with event processing --- .../UtmModuleGroupService.java | 57 +++++++++++++------ .../UtmModuleGroupResource.java | 4 +- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java index 4e0ca7b08..28e89d7a1 100644 --- a/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java +++ b/backend/src/main/java/com/park/utmstack/service/application_modules/UtmModuleGroupService.java @@ -88,36 +88,61 @@ public Optional findOne(Long id) { return moduleGroupRepository.findById(id); } - /** - * Delete the utmConfigurationGroup by id. - * - * @param id the id of the entity - */ - - public UtmModule delete(Long id) { - final String ctx = CLASSNAME + ".delete"; - long start = System.currentTimeMillis(); + @Transactional + public void deleteGroup(Long id) { UtmModuleGroup moduleGroup = this.moduleGroupRepository.findById(id) .orElseThrow(() -> new EntityNotFoundException("Configuration group not found with ID: " + id)); + Long moduleId = moduleGroup.getModule().getId(); String moduleName = String.valueOf(moduleGroup.getModule().getModuleName()); + Map extra = Map.of( - "ModuleId", moduleGroup.getModule().getId(), + "ModuleId", moduleId, "ModuleName", moduleName, "GroupId", id ); - String attemptMsg = String.format("Initiating deletion of configuration group (ID: %d) for module '%s'", id, moduleName); + String attemptMsg = String.format( + "Initiating deletion of configuration group (ID: %d) for module '%s'", + id, moduleName + ); applicationEventService.createEvent(attemptMsg, ApplicationEventType.CONFIG_GROUP_DELETE_ATTEMPT, extra); moduleGroupRepository.deleteById(id); - long duration = System.currentTimeMillis() - start; - String successMsg = String.format("Configuration group (ID: %d) for module '%s' deleted successfully in %dms", id, moduleName, duration); + String successMsg = String.format( + "Configuration group (ID: %d) for module '%s' deleted successfully", + id, moduleName + ); applicationEventService.createEvent(successMsg, ApplicationEventType.CONFIG_GROUP_DELETE_SUCCESS, extra); + } + + public void deleteAndFetch(Long id) { + + try { + Long moduleId = moduleGroupRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("Configuration group not found with ID: " + id)) + .getModule() + .getId(); + + deleteGroup(id); + + UtmModule module = moduleService.findOne(moduleId) + .orElseThrow(() -> new EntityNotFoundException("Module not found with id " + moduleId)); + + ModuleDTO moduleDTO = moduleMapper.toDto(module, false); + + moduleDTO.setModuleGroups( + moduleDTO.getModuleGroups().stream().filter(g -> !g.getId().equals(id)).collect(Collectors.toSet()) + ); + eventProcessorManagerService.updateModule(moduleDTO); + + } catch (Exception e) { + log.error("{}: Error deleting configuration group with ID {}: {}", CLASSNAME, id, e.getMessage()); + throw new ApiException(String.format("%s: Error deleting configuration group with ID %d", CLASSNAME, id), HttpStatus.INTERNAL_SERVER_ERROR); + } - return moduleGroup.getModule(); } @@ -150,7 +175,7 @@ public List findAllByModuleId(Long moduleId) throws Exception { public List findAllByCollectorId(String collectorId) { String ctx = CLASSNAME + ".findAllByCollectorId"; try { - return moduleGroupRepository.findAllByCollector(collectorId); + return moduleGroupRepository.findAllByCollector(collectorId); } catch (Exception e) { log.error("{}: Error finding module groups by collector id {}: {}", ctx, collectorId, e.getMessage()); throw new ApiException(String.format("%s: Error finding module groups by collector id %s", ctx, collectorId), HttpStatus.INTERNAL_SERVER_ERROR); @@ -224,7 +249,7 @@ public void updateCollectorConfigurationKeys(CollectorConfigDTO collectorConfig) moduleGroupRepository.deleteAll(dbConfigs); } else { for (UtmModuleGroupConfiguration key : keys) { - if (key.getConfDataType().equals("password")){ + if (key.getConfDataType().equals("password")) { key.setConfValue(CipherUtil.encrypt(key.getConfValue(), System.getenv(Constants.ENV_ENCRYPTION_KEY))); } } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/application_modules/UtmModuleGroupResource.java b/backend/src/main/java/com/park/utmstack/web/rest/application_modules/UtmModuleGroupResource.java index 62b2314d0..a8090ad4d 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/application_modules/UtmModuleGroupResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/application_modules/UtmModuleGroupResource.java @@ -160,9 +160,7 @@ public ResponseEntity getConfigurationGroup(@PathVariable Long g public ResponseEntity deleteSingleModuleGroup(@RequestParam Long groupId) { final String ctx = CLASSNAME + ".deleteSingleModuleGroup"; - UtmModule module = moduleGroupService.delete(groupId); - ModuleDTO moduleDTO = moduleMapper.toDto(module, false); - eventProcessorManagerService.updateModule(moduleDTO); + moduleGroupService.deleteAndFetch(groupId); return ResponseEntity.ok().build(); } From a84fcc0ac550a45e91cd43d024b8fdabb023ce23 Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Wed, 11 Mar 2026 22:12:10 +0300 Subject: [PATCH 199/240] refactor(crowdstrike): migrate from polling to real-time event streaming - Replaced polling-based event collection with real-time streaming architecture - Implemented persistent stream management with automatic reconnection - Added dynamic configuration reloading with live stream updates - Introduced per-stream offset tracking to prevent duplicate events --- plugins/crowdstrike/check.go | 2 +- plugins/crowdstrike/config/config.go | 25 +- plugins/crowdstrike/main.go | 463 +++++++++++++++++++-------- 3 files changed, 348 insertions(+), 142 deletions(-) diff --git a/plugins/crowdstrike/check.go b/plugins/crowdstrike/check.go index 063aa7cd4..d5c55e50f 100644 --- a/plugins/crowdstrike/check.go +++ b/plugins/crowdstrike/check.go @@ -58,7 +58,7 @@ func infiniteRetryIfXError(f func() error, exception string) error { _ = catcher.Error("An error occurred (%s), will keep retrying indefinitely...", err, map[string]any{"process": "plugin_com.utmstack.crowdstrike"}) xErrorWasLogged = true } - time.Sleep(wait) + time.Sleep(reconnectDelay) continue } diff --git a/plugins/crowdstrike/config/config.go b/plugins/crowdstrike/config/config.go index beacd83c3..4cb1926be 100644 --- a/plugins/crowdstrike/config/config.go +++ b/plugins/crowdstrike/config/config.go @@ -23,13 +23,17 @@ const ( ) var ( - cnf *ConfigurationSection - mu sync.Mutex - + cnf *ConfigurationSection + mu sync.Mutex + configUpdateChan chan *ConfigurationSection internalKey string modulesConfigHost string ) +func init() { + configUpdateChan = make(chan *ConfigurationSection, 1) +} + func GetConfig() *ConfigurationSection { mu.Lock() defer mu.Unlock() @@ -39,6 +43,10 @@ func GetConfig() *ConfigurationSection { return cnf } +func GetConfigUpdateChannel() <-chan *ConfigurationSection { + return configUpdateChan +} + func StartConfigurationSystem() { for { pluginConfig := plugins.PluginCfg("com.utmstack") @@ -133,7 +141,18 @@ func StartConfigurationSystem() { switch message := in.Payload.(type) { case *BiDirectionalMessage_Config: catcher.Info("Received configuration update", map[string]any{"process": "plugin_com.utmstack.crowdstrike"}) + + mu.Lock() cnf = message.Config + mu.Unlock() + + select { + case configUpdateChan <- message.Config: + + default: + + catcher.Info("Configuration update channel full, skipping notification", map[string]any{"process": "plugin_com.utmstack.crowdstrike"}) + } } } } diff --git a/plugins/crowdstrike/main.go b/plugins/crowdstrike/main.go index 8ced226be..f07a4b67b 100644 --- a/plugins/crowdstrike/main.go +++ b/plugins/crowdstrike/main.go @@ -14,6 +14,7 @@ import ( "github.com/crowdstrike/gofalcon/falcon/client" "github.com/crowdstrike/gofalcon/falcon/client/event_streams" "github.com/crowdstrike/gofalcon/falcon/models" + "github.com/crowdstrike/gofalcon/falcon/models/streaming_models" "github.com/google/uuid" "github.com/threatwinds/go-sdk/catcher" "github.com/threatwinds/go-sdk/plugins" @@ -23,7 +24,28 @@ import ( const ( defaultTenant = "ce66672c-e36d-4761-a8c8-90058fee1a24" urlCheckConnection = "https://falcon.crowdstrike.com" - wait = 1 * time.Second + reconnectDelay = 5 * time.Second +) + +type streamKey struct { + groupID int32 + groupName string +} + +type activeStream struct { + ctx context.Context + cancel context.CancelFunc + processor *CrowdStrikeProcessor + dataSource string + offsets map[string]uint64 + streamStartTime uint64 + wg sync.WaitGroup + mu sync.Mutex +} + +var ( + activeStreams = make(map[streamKey]*activeStream) + activeStreamsMu sync.RWMutex ) func main() { @@ -32,70 +54,305 @@ func main() { return } + if err := connectionChecker(urlCheckConnection); err != nil { + _ = catcher.Error("Failed to establish connectivity, plugin will not start", err, map[string]any{ + "process": "plugin_com.utmstack.crowdstrike", + }) + return + } + go config.StartConfigurationSystem() - for t := 0; t < 2*runtime.NumCPU(); t++ { + for i := 0; i < 2*runtime.NumCPU(); i++ { go func() { plugins.SendLogsFromChannel("com.utmstack.crowdstrike") }() } - delay := 5 * time.Minute - ticker := time.NewTicker(delay) - defer ticker.Stop() + go watchConfigurationChanges() - for range ticker.C { - if err := connectionChecker(urlCheckConnection); err != nil { - _ = catcher.Error("External connection failure detected: %v", err, map[string]any{"process": "plugin_com.utmstack.crowdstrike"}) + select {} +} + +func watchConfigurationChanges() { + time.Sleep(3 * time.Second) + + initialConfig := config.GetConfig() + if initialConfig != nil && initialConfig.ModuleActive { + updateStreams(initialConfig) + } + + for newConfig := range config.GetConfigUpdateChannel() { + if newConfig == nil || !newConfig.ModuleActive { + stopAllStreams() + continue } - moduleConfig := config.GetConfig() - if moduleConfig != nil && moduleConfig.ModuleActive { - var wg sync.WaitGroup - wg.Add(len(moduleConfig.ModuleGroups)) - for _, grp := range moduleConfig.ModuleGroups { - go func(group *config.ModuleGroup) { - defer wg.Done() - var invalid bool - for _, c := range group.ModuleGroupConfigurations { - if strings.TrimSpace(c.ConfValue) == "" { - invalid = true - break - } - } - - if !invalid { - pullCrowdStrikeEvents(group) - } - }(grp) + updateStreams(newConfig) + } +} + +func updateStreams(newConfig *config.ConfigurationSection) { + activeStreamsMu.Lock() + defer activeStreamsMu.Unlock() + + newGroups := make(map[streamKey]*config.ModuleGroup) + for _, grp := range newConfig.ModuleGroups { + key := streamKey{groupID: grp.Id, groupName: grp.GroupName} + newGroups[key] = grp + } + + for key, stream := range activeStreams { + if _, exists := newGroups[key]; !exists { + stream.cancel() + + go func(s *activeStream, k streamKey) { + s.wg.Wait() + activeStreamsMu.Lock() + delete(activeStreams, k) + activeStreamsMu.Unlock() + }(stream, key) + } + } + + for key, group := range newGroups { + if !isGroupValid(group) { + continue + } + + existingStream, exists := activeStreams[key] + + if exists { + newProcessor := buildProcessor(group) + if processorChanged(existingStream.processor, newProcessor) { + existingStream.cancel() + + go func(s *activeStream, k streamKey, g *config.ModuleGroup) { + s.wg.Wait() + activeStreamsMu.Lock() + delete(activeStreams, k) + startStream(k, g) + activeStreamsMu.Unlock() + }(existingStream, key, group) + } + } else { + startStream(key, group) + } + } +} + +func startStream(key streamKey, group *config.ModuleGroup) { + ctx, cancel := context.WithCancel(context.Background()) + + processor := buildProcessor(group) + + stream := &activeStream{ + ctx: ctx, + cancel: cancel, + processor: processor, + dataSource: group.GroupName, + offsets: make(map[string]uint64), + streamStartTime: uint64(time.Now().UnixMilli()), + } + + activeStreams[key] = stream + + go maintainStreamConnection(stream) +} + +func stopAllStreams() { + activeStreamsMu.Lock() + + if len(activeStreams) == 0 { + activeStreamsMu.Unlock() + return + } + + for _, stream := range activeStreams { + stream.cancel() + } + + var wg sync.WaitGroup + for _, stream := range activeStreams { + wg.Add(1) + go func(s *activeStream) { + defer wg.Done() + s.wg.Wait() + }(stream) + } + + activeStreamsMu.Unlock() + + wg.Wait() + + activeStreamsMu.Lock() + for key := range activeStreams { + delete(activeStreams, key) + } + activeStreamsMu.Unlock() +} + +func maintainStreamConnection(stream *activeStream) { + for { + err := runEventStream(stream) + if err != nil { + select { + case <-stream.ctx.Done(): + return + case <-time.After(reconnectDelay): } - wg.Wait() } } } -func pullCrowdStrikeEvents(group *config.ModuleGroup) { - processor := getCrowdStrikeProcessor(group) +func runEventStream(stream *activeStream) error { + apiClient, err := stream.processor.createClient() + if err != nil { + return catcher.Error("failed to create client", err, map[string]any{ + "process": "plugin_com.utmstack.crowdstrike", + }) + } - events, err := processor.getEvents() + ctx, cancel := context.WithTimeout(stream.ctx, 2*time.Minute) + defer cancel() + + jsonFormat := "json" + response, err := apiClient.EventStreams.ListAvailableStreamsOAuth2( + &event_streams.ListAvailableStreamsOAuth2Params{ + AppID: stream.processor.AppName, + Format: &jsonFormat, + Context: ctx, + }, + ) if err != nil { - _ = catcher.Error("cannot get CrowdStrike events", err, map[string]any{ - "group": group.GroupName, + return catcher.Error("failed to list streams", err, map[string]any{ "process": "plugin_com.utmstack.crowdstrike", }) - return } - for _, event := range events { - _ = plugins.EnqueueLog(&plugins.Log{ - Id: uuid.NewString(), - TenantId: defaultTenant, - DataType: "crowdstrike", - DataSource: group.GroupName, - Timestamp: time.Now().UTC().Format(time.RFC3339Nano), - Raw: event, - }, "com.utmstack.crowdstrike") + if err = falcon.AssertNoError(response.Payload.Errors); err != nil { + return catcher.Error("CrowdStrike API error", err, map[string]any{ + "process": "plugin_com.utmstack.crowdstrike", + }) + } + + availableStreams := response.Payload.Resources + + for _, streamV2 := range availableStreams { + if streamV2.DataFeedURL == nil { + catcher.Error("Stream has nil DataFeedURL, skipping", nil, map[string]any{ + "process": "plugin_com.utmstack.crowdstrike", + }) + continue + } + + streamID := *streamV2.DataFeedURL + + stream.wg.Add(1) + go func(streamResource *models.MainAvailableStreamV2, sid string) { + defer stream.wg.Done() + maintainIndividualStream(stream, apiClient, streamResource, sid) + }(streamV2, streamID) } + + <-stream.ctx.Done() + + stream.wg.Wait() + + return nil +} + +func maintainIndividualStream(stream *activeStream, apiClient *client.CrowdStrikeAPISpecification, + streamResource *models.MainAvailableStreamV2, streamID string) { + + for { + select { + case <-stream.ctx.Done(): + return + default: + stream.mu.Lock() + currentOffset := stream.offsets[streamID] + stream.mu.Unlock() + + falconStream, err := falcon.NewStream(stream.ctx, apiClient, stream.processor.AppName, streamResource, currentOffset) + if err != nil { + catcher.Error("failed to create stream, will retry", err, map[string]any{ + "process": "plugin_com.utmstack.crowdstrike", + }) + } else { + err = processStreamEvents(stream, falconStream, streamID) + falconStream.Close() + + if err != nil { + catcher.Error("stream error, will reconnect", err, map[string]any{ + "process": "plugin_com.utmstack.crowdstrike", + }) + } + } + + if err != nil { + select { + case <-stream.ctx.Done(): + return + case <-time.After(reconnectDelay): + continue + } + } + } + } +} + +func processStreamEvents(stream *activeStream, falconStream *falcon.StreamingHandle, streamID string) error { + for { + select { + case <-stream.ctx.Done(): + return nil + + case err := <-falconStream.Errors: + if err.Fatal { + return catcher.Error("fatal stream error", err.Err, map[string]any{ + "process": "plugin_com.utmstack.crowdstrike", + }) + } + catcher.Error("Non-fatal stream error", err.Err, map[string]any{ + "process": "plugin_com.utmstack.crowdstrike", + }) + + case event := <-falconStream.Events: + if event.Metadata.EventCreationTime > stream.streamStartTime { + processEvent(event, stream.dataSource) + + stream.mu.Lock() + stream.offsets[streamID] = event.Metadata.Offset + stream.mu.Unlock() + } + } + } +} + +func processEvent(event *streaming_models.EventItem, dataSource string) { + var eventData string + if len(event.RawMessage) > 0 { + eventData = string(event.RawMessage) + } else { + eventJSON, err := json.Marshal(event) + if err != nil { + catcher.Error("Failed to marshal event", err, map[string]any{ + "process": "plugin_com.utmstack.crowdstrike", + }) + return + } + eventData = string(eventJSON) + } + + _ = plugins.EnqueueLog(&plugins.Log{ + Id: uuid.NewString(), + TenantId: defaultTenant, + DataType: "crowdstrike", + DataSource: dataSource, + Timestamp: time.Now().UTC().Format(time.RFC3339Nano), + Raw: eventData, + }, "com.utmstack.crowdstrike") } type CrowdStrikeProcessor struct { @@ -105,8 +362,21 @@ type CrowdStrikeProcessor struct { AppName string } -func getCrowdStrikeProcessor(group *config.ModuleGroup) CrowdStrikeProcessor { - processor := CrowdStrikeProcessor{} +func isGroupValid(group *config.ModuleGroup) bool { + if group == nil { + return false + } + + for _, cnf := range group.ModuleGroupConfigurations { + if strings.TrimSpace(cnf.ConfValue) == "" { + return false + } + } + return true +} + +func buildProcessor(group *config.ModuleGroup) *CrowdStrikeProcessor { + processor := &CrowdStrikeProcessor{} for _, cnf := range group.ModuleGroupConfigurations { switch cnf.ConfKey { @@ -123,6 +393,16 @@ func getCrowdStrikeProcessor(group *config.ModuleGroup) CrowdStrikeProcessor { return processor } +func processorChanged(old, new *CrowdStrikeProcessor) bool { + if old == nil || new == nil { + return true + } + return old.ClientID != new.ClientID || + old.ClientSecret != new.ClientSecret || + old.Cloud != new.Cloud || + old.AppName != new.AppName +} + func (p *CrowdStrikeProcessor) createClient() (*client.CrowdStrikeAPISpecification, error) { if p.ClientID == "" || p.ClientSecret == "" { return nil, catcher.Error("cannot create CrowdStrike client", @@ -172,96 +452,3 @@ func extractCloudFromURL(cloudValue string) (falcon.CloudType, error) { return falcon.CloudValidate(trimmed) } - -func (p *CrowdStrikeProcessor) getEvents() ([]string, error) { - client, err := p.createClient() - if err != nil { - return nil, err - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - defer cancel() - - json := "json" - response, err := client.EventStreams.ListAvailableStreamsOAuth2( - &event_streams.ListAvailableStreamsOAuth2Params{ - AppID: p.AppName, - Format: &json, - Context: ctx, - }, - ) - if err != nil { - return nil, catcher.Error("cannot list available streams", err, map[string]any{"process": "plugin_com.utmstack.crowdstrike"}) - } - - if err = falcon.AssertNoError(response.Payload.Errors); err != nil { - return nil, catcher.Error("CrowdStrike API error", err, map[string]any{"process": "plugin_com.utmstack.crowdstrike"}) - } - - availableStreams := response.Payload.Resources - if len(availableStreams) == 0 { - _ = catcher.Error("no available streams found", nil, map[string]any{ - "app_id": p.AppName, - "process": "plugin_com.utmstack.crowdstrike", - }) - return []string{}, nil - } - - var events []string - for _, availableStream := range availableStreams { - streamEvents, err := p.getStreamEvents(ctx, client, availableStream) - if err != nil { - _ = catcher.Error("cannot get stream events", err, map[string]any{ - "stream": availableStream, - "process": "plugin_com.utmstack.crowdstrike", - }) - continue - } - events = append(events, streamEvents...) - } - - return events, nil -} - -func (p *CrowdStrikeProcessor) getStreamEvents(ctx context.Context, client *client.CrowdStrikeAPISpecification, availableStream interface{}) ([]string, error) { - stream_v2, ok := availableStream.(*models.MainAvailableStreamV2) - if !ok { - return nil, catcher.Error("invalid stream type", fmt.Errorf("cannot convert to MainAvailableStreamV2"), map[string]any{"process": "plugin_com.utmstack.crowdstrike"}) - } - - stream, err := falcon.NewStream(ctx, client, p.AppName, stream_v2, 0) - if err != nil { - return nil, catcher.Error("cannot create stream", err, map[string]any{"process": "plugin_com.utmstack.crowdstrike"}) - } - defer stream.Close() - - var events []string - timeout := time.NewTimer(30 * time.Second) - defer timeout.Stop() - - for { - select { - case err := <-stream.Errors: - if err.Fatal { - return events, catcher.Error("fatal stream error", err.Err, map[string]any{"process": "plugin_com.utmstack.crowdstrike"}) - } else { - _ = catcher.Error("stream error", err.Err, map[string]any{"process": "plugin_com.utmstack.crowdstrike"}) - } - case event := <-stream.Events: - eventJSON, err := json.Marshal(event) - if err != nil { - _ = catcher.Error("cannot marshal event", err, map[string]any{"process": "crowdstrike-plugin"}) - continue - } - events = append(events, string(eventJSON)) - - if len(events) >= 100 { - return events, nil - } - case <-timeout.C: - return events, nil - case <-ctx.Done(): - return events, nil - } - } -} From 65062e80dca9321a198a32711df4c3b33a30b848 Mon Sep 17 00:00:00 2001 From: Manuel Abascal Date: Fri, 13 Mar 2026 09:20:03 -0500 Subject: [PATCH 200/240] fix: uncomment admin authority container in management sidebar Signed-off-by: Manuel Abascal --- .../app-management-sidebar.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/app-management/layout/app-management-sidebar/app-management-sidebar.component.html b/frontend/src/app/app-management/layout/app-management-sidebar/app-management-sidebar.component.html index f31ac2421..809572998 100644 --- a/frontend/src/app/app-management/layout/app-management-sidebar/app-management-sidebar.component.html +++ b/frontend/src/app/app-management/layout/app-management-sidebar/app-management-sidebar.component.html @@ -138,7 +138,7 @@ - + From 0d8227bac0b5bce0b552d38bb9c647455a94875c Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Fri, 13 Mar 2026 13:52:27 -0400 Subject: [PATCH 201/240] fix[backend](data-sources): fixed data sources variable source selection on filter --- backend/mvnw | 0 backend/pom.xml | 1 + .../UtmNetworkScanRepository.java | 33 ++++++++++++------- .../network_scan/UtmNetworkScanService.java | 27 ++++++++------- 4 files changed, 38 insertions(+), 23 deletions(-) mode change 100644 => 100755 backend/mvnw diff --git a/backend/mvnw b/backend/mvnw old mode 100644 new mode 100755 diff --git a/backend/pom.xml b/backend/pom.xml index 3d6f936e3..022fdd657 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -8,6 +8,7 @@ com.atlasinside utmstack ${revision} + war UTMStack-API diff --git a/backend/src/main/java/com/park/utmstack/repository/network_scan/UtmNetworkScanRepository.java b/backend/src/main/java/com/park/utmstack/repository/network_scan/UtmNetworkScanRepository.java index ee72ae4eb..af0158bd0 100644 --- a/backend/src/main/java/com/park/utmstack/repository/network_scan/UtmNetworkScanRepository.java +++ b/backend/src/main/java/com/park/utmstack/repository/network_scan/UtmNetworkScanRepository.java @@ -43,18 +43,18 @@ public interface UtmNetworkScanRepository extends JpaRepository searchByFilters(@Param("assetIpMacName") String assetIpMacName, @Param("assetOs") List assetOs, + @Param("hasAssetOs") boolean hasAssetOs, @Param("assetAlias") List assetAlias, + @Param("hasAssetAlias") boolean hasAssetAlias, @Param("assetType") List assetType, + @Param("hasAssetType") boolean hasAssetType, @Param("assetAlive") List assetAlive, + @Param("hasAssetAlive") boolean hasAssetAlive, @Param("assetStatus") List assetStatus, + @Param("hasAssetStatus") boolean hasAssetStatus, @Param("serverName") List serverName, + @Param("hasServerName") boolean hasServerName, @Param("ports") List ports, + @Param("hasPorts") boolean hasPorts, @Param("initDate") Instant initDate, @Param("endDate") Instant endDate, @Param("groups") List groups, + @Param("hasGroups") boolean hasGroups, @Param("registeredMode") AssetRegisteredMode registeredMode, @Param("isAgent") List isAgent, + @Param("hasIsAgent") boolean hasIsAgent, @Param("assetOsPlatform") List assetOsPlatform, + @Param("hasAssetOsPlatform") boolean hasAssetOsPlatform, @Param("dataTypes") List dataTypes, + @Param("hasDataTypes") boolean hasDataTypes, Pageable pageable); @Modifying diff --git a/backend/src/main/java/com/park/utmstack/service/network_scan/UtmNetworkScanService.java b/backend/src/main/java/com/park/utmstack/service/network_scan/UtmNetworkScanService.java index 4da77b211..1901434ba 100644 --- a/backend/src/main/java/com/park/utmstack/service/network_scan/UtmNetworkScanService.java +++ b/backend/src/main/java/com/park/utmstack/service/network_scan/UtmNetworkScanService.java @@ -274,20 +274,23 @@ public ByteArrayOutputStream getNetworkScanReport(NetworkScanFilter f, Pageable private Page filter(NetworkScanFilter f, Pageable p) throws Exception { final String ctx = CLASSNAME + ".filter"; try { - - /*if (page.getTotalPages() > 0) { - List utmDataInputStatuses = utmDataInputStatusRepository.findAll().stream().sorted(Comparator.comparing(UtmDataInputStatus::getSource)).collect(Collectors.toList()); - page.forEach(m -> m.setMetrics(assetMetricsRepository.findAllByAssetName(m.getAssetName()))); - page.forEach(m -> m.setDataInputList(utmDataInputStatuses.stream().filter( - inputStatus -> inputStatus.getSource().equalsIgnoreCase(m.getAssetName()) || - inputStatus.getSource().equalsIgnoreCase(m.getAssetIp())).collect(Collectors.toList()))); - }*/ - return networkScanRepository.searchByFilters( f.getAssetIpMacName() == null ? null : "%" + f.getAssetIpMacName() + "%", - f.getOs(), f.getAlias(), f.getType(), f.getAlive(), f.getStatus(), - f.getProbe(), f.getOpenPorts(), f.getDiscoveredInitDate(), - f.getDiscoveredEndDate(), f.getGroups(), f.getRegisteredMode(), f.getAgent(), f.getOsPlatform(), f.getDataTypes(), p); + f.getOs(), !CollectionUtils.isEmpty(f.getOs()), + f.getAlias(), !CollectionUtils.isEmpty(f.getAlias()), + f.getType(), !CollectionUtils.isEmpty(f.getType()), + f.getAlive(), !CollectionUtils.isEmpty(f.getAlive()), + f.getStatus(), !CollectionUtils.isEmpty(f.getStatus()), + f.getProbe(), !CollectionUtils.isEmpty(f.getProbe()), + f.getOpenPorts(), !CollectionUtils.isEmpty(f.getOpenPorts()), + f.getDiscoveredInitDate(), + f.getDiscoveredEndDate(), + f.getGroups(), !CollectionUtils.isEmpty(f.getGroups()), + f.getRegisteredMode(), + f.getAgent(), !CollectionUtils.isEmpty(f.getAgent()), + f.getOsPlatform(), !CollectionUtils.isEmpty(f.getOsPlatform()), + f.getDataTypes(), !CollectionUtils.isEmpty(f.getDataTypes()), + p); } catch (InvalidDataAccessResourceUsageException e) { String msg = ctx + ": " + e.getMostSpecificCause().getMessage().replaceAll("\n", ""); throw new Exception(msg); From e20ce08dbd7060f315937c0936861235da47d049 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Mon, 16 Mar 2026 12:31:27 -0400 Subject: [PATCH 202/240] fix[frontend](o365_integration_guide): removed unneeded steps and signalized security indications about storing certificates --- .../guide-office365.component.html | 30 ++----------------- frontend/src/environments/environment.ts | 4 +-- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/frontend/src/app/app-module/guides/guide-office365/guide-office365.component.html b/frontend/src/app/app-module/guides/guide-office365/guide-office365.component.html index f187ee87a..9f29021e0 100644 --- a/frontend/src/app/app-module/guides/guide-office365/guide-office365.component.html +++ b/frontend/src/app/app-module/guides/guide-office365/guide-office365.component.html @@ -41,7 +41,7 @@

    4 In the newly created App registration, go to Certificates & Secrets, and create a new one by clicking on "New client secret". - Add a description. Set it to expires in 730 days (24 months) and click on Add. Copy the value in a safe place. + Add a description. Set it to expires in 730 days (24 months) and click on Add. Copy the value in a safe place.

    Certificates & secret @@ -94,30 +94,6 @@
  4. 10 - Go to - https://compliance.microsoft.com and sign in. -

    - -
  5. -
  6. -

    - 11 - In the left navigation pane of the compliance portal, select Audit. If auditing isn't turned on for your organization, - a banner is displayed prompting you start recording user and admin activity. -

    - Overview -
  7. -
  8. -

    - 12 - Select the Start recording user and admin activity banner. It may take up to 60 minutes for the change to take effect. -

    -
  9. -
  10. -

    - 13 Return to Azure Active Directory and go to the Registered Apps (step number 3) and make a note the info that appears in the Overview section (client ID, tenant ID) and the secret.

    @@ -126,7 +102,7 @@
  11. - 14 + 11 Insert information in the following inputs.You can add more than one o365 configuration by clicking on Add tenant button.

    @@ -140,7 +116,7 @@
  12. - 15 + 12 Click on the button shown below, to activate the UTMStack features related to this integration

    Date: Mon, 16 Mar 2026 13:41:36 -0400 Subject: [PATCH 203/240] changeset[backend](rules): added 'Windows Token Manipulation' removal change set' --- ...6001_remove_windows_token_manipulation_rule.xml | 14 ++++++++++++++ .../src/main/resources/config/liquibase/master.xml | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260316001_remove_windows_token_manipulation_rule.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316001_remove_windows_token_manipulation_rule.xml b/backend/src/main/resources/config/liquibase/changelog/20260316001_remove_windows_token_manipulation_rule.xml new file mode 100644 index 000000000..faadd5e47 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260316001_remove_windows_token_manipulation_rule.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index ddba358f1..a04f2f66b 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -511,5 +511,7 @@ + + From 972779272c15430ed57afab59e108d4c50a4bfe1 Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Mon, 16 Mar 2026 20:53:06 +0300 Subject: [PATCH 204/240] feat(filters/azure): add WAF properties and client connection field mappings --- filters/azure/azure-eventhub.yml | 192 ++++++++++++++++++++++++++++++- 1 file changed, 191 insertions(+), 1 deletion(-) diff --git a/filters/azure/azure-eventhub.yml b/filters/azure/azure-eventhub.yml index 1ffaf26f7..2ec401f8b 100644 --- a/filters/azure/azure-eventhub.yml +++ b/filters/azure/azure-eventhub.yml @@ -1,4 +1,4 @@ -# Azure Envent-Hub filter, version 2.0.4 +# Azure Envent-Hub filter, version 2.0.5 # # Documentations # 1- https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log @@ -603,6 +603,196 @@ pipeline: - log.properties.userType to: log.propertiesUserType + - rename: + from: + - log.properties.WAFEvaluationTime + to: log.propertiesWAFEvaluationTime + + - rename: + from: + - log.properties.WAFMode + to: log.propertiesWAFMode + + - rename: + from: + - log.properties.WAFPolicyID + to: log.propertiesWAFPolicyID + + - rename: + from: + - log.properties.backendSslCipher + to: log.propertiesBackendSslCipher + + - rename: + from: + - log.properties.backendSslProtocol + to: log.propertiesBackendSslProtocol + + - rename: + from: + - log.properties.clientIP + to: origin.ip + + - rename: + from: + - log.properties.clientPort + to: origin.port + + - rename: + from: + - log.properties.clientResponseTime + to: log.propertiesClientResponseTime + + - rename: + from: + - log.properties.connectionSerialNumber + to: log.propertiesConnectionSerialNumber + + - rename: + from: + - log.properties.contentType + to: log.propertiesContentType + + - rename: + from: + - log.properties.error_info + to: log.propertiesErrorInfo + + - rename: + from: + - log.properties.host + to: origin.host + + - rename: + from: + - log.properties.httpMethod + to: log.propertiesHttpMethod + + - rename: + from: + - log.properties.httpStatus + to: statusCode + + - rename: + from: + - log.properties.httpVersion + to: log.propertiesHttpVersion + + - rename: + from: + - log.properties.instanceId + to: log.propertiesInstanceId + + - rename: + from: + - log.properties.noOfConnectionRequests + to: log.propertiesNoOfConnectionRequests + + - rename: + from: + - log.properties.originalHost + to: log.propertiesOriginalHost + + - rename: + from: + - log.properties.originalRequestUriWithArgs + to: log.propertiesOriginalRequestUriWithArgs + + - rename: + from: + - log.properties.receivedBytes + to: log.propertiesReceivedBytes + + - rename: + from: + - log.properties.requestQuery + to: log.propertiesRequestQuery + + - rename: + from: + - log.properties.requestUri + to: log.propertiesRequestUri + + - rename: + from: + - log.properties.sentBytes + to: log.propertiesSentBytes + + - rename: + from: + - log.properties.serverConnectTime + to: log.propertiesServerConnectTime + + - rename: + from: + - log.properties.serverHeaderTime + to: log.propertiesServerHeaderTime + + - rename: + from: + - log.properties.serverResponseLatency + to: log.propertiesServerResponseLatency + + - rename: + from: + - log.properties.serverRouted + to: log.propertiesServerRouted + + - rename: + from: + - log.properties.serverStatus + to: log.propertiesServerStatus + + - rename: + from: + - log.properties.sslCipher + to: log.propertiesSslCipher + + - rename: + from: + - log.properties.sslClientCertificateFingerprint + to: log.propertiesSslClientCertificateFingerprint + + - rename: + from: + - log.properties.sslClientCertificateIssuerName + to: log.propertiesSslClientCertificateIssuerName + + - rename: + from: + - log.properties.sslClientVerify + to: log.propertiesSslClientVerify + + - rename: + from: + - log.properties.sslEnabled + to: log.propertiesSslEnabled + + - rename: + from: + - log.properties.sslProtocol + to: log.propertiesSslProtocol + + - rename: + from: + - log.properties.timeTaken + to: log.propertiesTimeTaken + + - rename: + from: + - log.properties.transactionId + to: log.propertiesTransactionId + + - rename: + from: + - log.properties.upstreamSourcePort + to: log.propertiesUpstreamSourcePort + + - rename: + from: + - log.properties.userAgent + to: log.propertiesUserAgent + # .......................................................................# # Adding severity based on log.level # .......................................................................# From 14db80cdc0975995f1bbd0af9ddf8f1eecdb8ae5 Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Mon, 16 Mar 2026 20:53:56 +0300 Subject: [PATCH 205/240] feat(filters/crowdstrike): refactor field mappings and add detection support --- filters/crowdstrike/crowdstrike.yml | 513 +++++++++++++++++++++++++--- 1 file changed, 459 insertions(+), 54 deletions(-) diff --git a/filters/crowdstrike/crowdstrike.yml b/filters/crowdstrike/crowdstrike.yml index 72eb5c40c..ab6bcd03d 100644 --- a/filters/crowdstrike/crowdstrike.yml +++ b/filters/crowdstrike/crowdstrike.yml @@ -1,4 +1,4 @@ -# Crowdstrike module filter, version 1.1.1 +# Crowdstrike module filter, version 1.2.0 # Based in docs and samples provided # # Documentations @@ -16,218 +16,630 @@ pipeline: # .......................................................................# - rename: from: - - log.RawMessage.event.Attributes.APIClientID + - log.event.Attributes.APIClientID to: log.eventAttributesAPIClientID - rename: from: - - log.RawMessage.event.Attributes.actor_cid + - log.event.Attributes.actor_cid to: log.eventAttributesActorCid - rename: from: - - log.RawMessage.event.Attributes.actor_user + - log.event.Attributes.actor_user to: log.eventAttributesActorUser - rename: from: - - log.RawMessage.event.Attributes.actor_user_uuid + - log.event.Attributes.actor_user_uuid to: log.eventAttributesActorUserUUID - rename: from: - - log.RawMessage.event.Attributes.name + - log.event.Attributes.name to: log.eventAttributesName - rename: from: - - log.RawMessage.event.Attributes.trace_id + - log.event.Attributes.trace_id to: log.eventAttributesTraceID - rename: from: - - log.RawMessage.event.Attributes.cid + - log.event.Attributes.cid to: log.eventAttributesCid - rename: from: - - log.RawMessage.event.Attributes.consumes + - log.event.Attributes.consumes to: log.eventAttributesConsumes - rename: from: - - log.RawMessage.event.Attributes.elapsed_microseconds + - log.event.Attributes.elapsed_microseconds to: log.eventAttributesElapsedMicroseconds - rename: from: - - log.RawMessage.event.Attributes.elapsed_time + - log.event.Attributes.elapsed_time to: log.eventAttributesElapsedTime - rename: from: - - log.RawMessage.event.Attributes.produces + - log.event.Attributes.produces to: log.eventAttributesProduces - rename: from: - - log.RawMessage.event.Attributes.received_time + - log.event.Attributes.received_time to: log.eventAttributesReceivedTime - rename: from: - - log.RawMessage.event.Attributes.request_content_type + - log.event.Attributes.request_content_type to: log.eventAttributesRequestContentType - rename: from: - - log.RawMessage.event.Attributes.request_method + - log.event.Attributes.request_method to: log.eventAttributesRequestMethod - rename: from: - - log.RawMessage.event.Attributes.request_uri_length + - log.event.Attributes.request_uri_length to: log.eventAttributesRequestURILength - rename: from: - - log.RawMessage.event.Attributes.status_code + - log.event.Attributes.status_code to: log.statusCode - rename: from: - - log.RawMessage.event.Attributes.sub_component_1 + - log.event.Attributes.sub_component_1 to: log.eventAttributesSubComponent1 - rename: from: - - log.RawMessage.event.Attributes.sub_component_2 + - log.event.Attributes.sub_component_2 to: log.eventAttributesSubComponent2 - rename: from: - - log.RawMessage.event.Attributes.sub_component_3 + - log.event.Attributes.sub_component_3 to: log.eventAttributesSubComponent3 - rename: from: - - log.RawMessage.event.Attributes.trace_id + - log.event.Attributes.trace_id to: log.eventAttributesTraceID - rename: from: - - log.RawMessage.event.Attributes.user_agent + - log.event.Attributes.user_agent to: log.eventAttributesUserAgent - rename: from: - - log.RawMessage.event.Attributes.eventType + - log.event.Attributes.eventType to: log.eventAttributesEventType - rename: from: - - log.RawMessage.event.Attributes.offset + - log.event.Attributes.offset to: log.eventAttributesOffset - rename: from: - - log.RawMessage.event.Attributes.partition + - log.event.Attributes.partition to: log.eventAttributesPartition - rename: from: - - log.RawMessage.event.Attributes.request_accept + - log.event.Attributes.request_accept to: log.eventAttributesRequestAccept - rename: from: - - log.RawMessage.event.Attributes.request_path + - log.event.Attributes.request_path to: log.eventAttributesRequestPath - rename: from: - - log.RawMessage.event.Attributes.request_query + - log.event.Attributes.request_query to: log.eventAttributesRequestQuery - rename: from: - - log.RawMessage.event.Attributes.scopes + - log.event.Attributes.scopes to: log.eventAttributesScopes - rename: from: - - log.RawMessage.event.AuditKeyValues + - log.event.Attributes.target_name + to: log.eventAttributesTargetName + + - rename: + from: + - log.event.Attributes.success + to: log.eventAttributesSuccess + + - rename: + from: + - log.event.Attributes.target_cid + to: log.eventAttributesTargetCID + + - rename: + from: + - log.event.Attributes.target_uuid + to: log.eventAttributesTargetUUID + + - rename: + from: + - log.event.Attributes.id + to: log.eventAttributesID + + - rename: + from: + - log.event.Attributes.scope(s) + to: log.eventAttributesScopes + + - rename: + from: + - log.event.Attributes.appId + to: log.eventAttributesAppId + + - rename: + from: + - log.event.AuditKeyValues to: log.eventAuditKeyValues - rename: from: - - log.RawMessage.event.Message + - log.event.Message to: log.eventMessage - rename: from: - - log.RawMessage.event.OperationName + - log.event.OperationName to: log.eventOperationName - rename: from: - - log.RawMessage.event.ServiceName + - log.event.ServiceName to: log.eventServiceName - rename: from: - - log.RawMessage.event.Source + - log.event.Source to: log.eventSource - rename: from: - - log.RawMessage.event.ServiceName + - log.event.ServiceName to: log.eventServiceName - rename: from: - - log.RawMessage.event.SourceIp + - log.event.SourceIp to: origin.ip - rename: from: - - log.RawMessage.event.Success + - log.event.Success to: log.eventSuccess - rename: from: - - log.RawMessage.event.UTCTimestamp + - log.event.UTCTimestamp to: log.eventUTCTimestamp - rename: from: - - log.RawMessage.event.UserId + - log.event.UserId to: log.eventUserId - rename: from: - - log.RawMessage.metadata.customerIDString + - log.metadata.customerIDString to: log.metadataCustomerIDString - rename: from: - - log.RawMessage.metadata.eventCreationTime + - log.metadata.eventCreationTime to: log.metadataEventCreationTime - rename: from: - - log.RawMessage.metadata.eventType + - log.metadata.eventType to: log.metadataEventType - rename: from: - - log.RawMessage.metadata.offset + - log.metadata.offset to: log.metadataOffset - rename: from: - - log.RawMessage.metadata.version + - log.metadata.version to: log.metadataVersion + + - rename: + from: + - log.event.Message + to: log.eventMessage + + - rename: + from: + - log.event.AgentId + to: log.eventAgentId + + - rename: + from: + - log.event.AggregateId + to: log.eventAggregateId + + - rename: + from: + - log.event.CloudIndicator + to: log.eventCloudIndicator + + - rename: + from: + - log.event.CommandLine + to: log.eventCommandLine + + - rename: + from: + - log.event.CompositeId + to: log.eventCompositeId + + - rename: + from: + - log.event.DataDomains + to: log.eventDataDomains + + - rename: + from: + - log.event.Description + to: log.eventDescription + + - rename: + from: + - log.event.FalconHostLink + to: log.eventFalconHostLink + + - rename: + from: + - log.event.FileName + to: origin.filename + + - rename: + from: + - log.event.FilePath + to: origin.path + + - rename: + from: + - log.event.GrandParentCommandLine + to: log.eventGrandParentCommandLine + + - rename: + from: + - log.event.GrandParentImageFileName + to: log.eventGrandParentImageFileName + + - rename: + from: + - log.event.GrandParentImageFilePath + to: log.eventGrandParentImageFilePath + + - rename: + from: + - log.event.Hostname + to: origin.host + + - rename: + from: + - log.event.LocalIP + to: origin.ip + + - rename: + from: + - log.event.LocalIPv6 + to: log.eventLocalIPv6 + + - rename: + from: + - log.event.LogonDomain + to: log.eventLogonDomain + + - rename: + from: + - log.event.MACAddress + to: origin.mac + + - rename: + from: + - log.event.MD5String + to: origin.md5 + + - rename: + from: + - log.event.MitreAttack + to: log.eventMitreAttack + + - rename: + from: + - log.event.Name + to: log.eventName + + - rename: + from: + - log.event.Objective + to: log.eventObjective + + - rename: + from: + - log.event.ParentCommandLine + to: log.eventParentCommandLine + + - rename: + from: + - log.event.ParentImageFileName + to: log.eventParentImageFileName + + - rename: + from: + - log.event.ParentImageFilePath + to: log.eventParentImageFilePath + + - rename: + from: + - log.event.ParentProcessId + to: log.eventParentProcessId + + - rename: + from: + - log.event.PatternDispositionDescription + to: log.eventPatternDispositionDescription + + - rename: + from: + - log.event.PatternDispositionFlags.BlockingUnsupportedOrDisabled + to: log.eventPatternDispositionFlagsBlockingUnsupportedOrDisabled + + - rename: + from: + - log.event.PatternDispositionFlags.BootupSafeguardEnabled + to: log.eventPatternDispositionFlagsBootupSafeguardEnabled + + - rename: + from: + - log.event.PatternDispositionFlags.ContainmentFileSystem + to: log.eventPatternDispositionFlagsContainmentFileSystem + + - rename: + from: + - log.event.PatternDispositionFlags.CriticalProcessDisabled + to: log.eventPatternDispositionFlagsCriticalProcessDisabled + + - rename: + from: + - log.event.PatternDispositionFlags.Detect + to: log.eventPatternDispositionFlagsDetect + + - rename: + from: + - log.event.PatternDispositionFlags.FsOperationBlocked + to: log.eventPatternDispositionFlagsFsOperationBlocked + + - rename: + from: + - log.event.PatternDispositionFlags.HandleOperationDowngraded + to: log.eventPatternDispositionFlagsHandleOperationDowngraded + + - rename: + from: + - log.event.PatternDispositionFlags.InddetMask + to: log.eventPatternDispositionFlagsInddetMask + + - rename: + from: + - log.event.PatternDispositionFlags.Indicator + to: log.eventPatternDispositionFlagsIndicator + + - rename: + from: + - log.event.PatternDispositionFlags.KillActionFailed + to: log.eventPatternDispositionFlagsKillActionFailed + + - rename: + from: + - log.event.PatternDispositionFlags.KillParent + to: log.eventPatternDispositionFlagsKillParent + + - rename: + from: + - log.event.PatternDispositionFlags.KillProcess + to: log.eventPatternDispositionFlagsKillProcess + + - rename: + from: + - log.event.PatternDispositionFlags.KillSubProcess + to: log.eventPatternDispositionFlagsKillSubProcess + + - rename: + from: + - log.event.PatternDispositionFlags.OperationBlocked + to: log.eventPatternDispositionFlagsOperationBlocked + + - rename: + from: + - log.event.PatternDispositionFlags.PolicyDisabled + to: log.eventPatternDispositionFlagsPolicyDisabled + + - rename: + from: + - log.event.PatternDispositionFlags.ProcessBlocked + to: log.eventPatternDispositionFlagsProcessBlocked + + - rename: + from: + - log.event.PatternDispositionFlags.QuarantineFile + to: log.eventPatternDispositionFlagsQuarantineFile + + - rename: + from: + - log.event.PatternDispositionFlags.QuarantineMachine + to: log.eventPatternDispositionFlagsQuarantineMachine + + - rename: + from: + - log.event.PatternDispositionFlags.RegistryOperationBlocked + to: log.eventPatternDispositionFlagsRegistryOperationBlocked + + - rename: + from: + - log.event.PatternDispositionFlags.Rooting + to: log.eventPatternDispositionFlagsRooting + + - rename: + from: + - log.event.PatternDispositionFlags.SensorOnly + to: log.eventPatternDispositionFlagsSensorOnly + + - rename: + from: + - log.event.PatternDispositionFlags.SuspendParent + to: log.eventPatternDispositionFlagsSuspendParent + + - rename: + from: + - log.event.PatternDispositionFlags.SuspendProcess + to: log.eventPatternDispositionFlagsSuspendProcess + + - rename: + from: + - log.event.PatternDispositionValue + to: log.eventPatternDispositionValue + + - rename: + from: + - log.event.PatternId + to: log.eventPatternId + + - rename: + from: + - log.event.PlatformId + to: log.eventPlatformId + + - rename: + from: + - log.event.PlatformName + to: origin.operatingSystem + + - rename: + from: + - log.event.ProcessEndTime + to: log.eventProcessEndTime + + - rename: + from: + - log.event.ProcessId + to: log.eventProcessId + + - rename: + from: + - log.event.ProcessStartTime + to: log.eventProcessStartTime + + - rename: + from: + - log.event.RiskScore + to: log.eventRiskScore + + - rename: + from: + - log.event.SHA1String + to: origin.sha1 + + - rename: + from: + - log.event.SHA256String + to: origin.sha256 + + - rename: + from: + - log.event.Severity + to: log.eventSeverity + + - rename: + from: + - log.event.SeverityName + to: log.eventSeverityName + + - rename: + from: + - log.event.SourceProducts + to: log.eventSourceProducts + + - rename: + from: + - log.event.SourceVendors + to: log.eventSourceVendors + + - rename: + from: + - log.event.Tactic + to: log.eventTactic + + - rename: + from: + - log.event.Technique + to: log.eventTechnique + + - rename: + from: + - log.event.Type + to: log.eventType + + - rename: + from: + - log.event.UserName + to: origin.user + + - rename: + from: + - log.event.FilesAccessed + to: log.eventFilesAccessed + + # .......................................................................# + # Remove inecesarie caracters + # .......................................................................# + - trim: + function: prefix + substring: '"' + fields: + - log.eventCommandLine + + - trim: + function: suffix + substring: '"' + fields: + - log.eventCommandLine + + - trim: + function: prefix + substring: '[{' + fields: + - log.eventCommandLine + + - trim: + function: suffix + substring: '}]' + fields: + - log.eventCommandLine # .......................................................................# # Reformat and field conversions @@ -299,13 +711,6 @@ pipeline: - delete: fields: - log.statusCode - - log.RawMessage.event.Attributes - - log.RawMessage.event.UserIp - - log.metadata - - log.event.AuditKeyValues - - log.event.OperationName - - log.event.ServiceName - - log.event.Success - - log.event.UTCTimestamp - - log.event.UserId - - log.event.UserIp \ No newline at end of file + - log.event.UserIp + - log.event.Attributes.user_ip + - log.event.Attributes.action_target_name \ No newline at end of file From 334a06140229514b6a3c5e9e4e55a7ca1a5d21b4 Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Mon, 16 Mar 2026 20:54:25 +0300 Subject: [PATCH 206/240] feat(filters/filebeat): add CPU and memory usage fields for systemd units --- filters/filebeat/system_linux_module.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/filters/filebeat/system_linux_module.yml b/filters/filebeat/system_linux_module.yml index 5bbbac822..eaac62e6b 100644 --- a/filters/filebeat/system_linux_module.yml +++ b/filters/filebeat/system_linux_module.yml @@ -287,6 +287,24 @@ pipeline: to: log.messageId where: exists("log.MESSAGEID") + - rename: + from: + - log.CPUUSAGENSEC + to: log.cpuUsageNsec + where: exists("log.CPUUSAGENSEC") + + - rename: + from: + - log.MEMORYPEAK + to: log.memoryPeak + where: exists("log.MEMORYPEAK") + + - rename: + from: + - log.MEMORYSWAPPEAK + to: log.memorySwapPeak + where: exists("log.MEMORYSWAPPEAK") + # ======================================== # PHASE 3: STANDARD SCHEMA MAPPING # ======================================== From cf3bd91a93ff00041daf37c3f56f644cae15a5cf Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Mon, 16 Mar 2026 20:54:55 +0300 Subject: [PATCH 207/240] feat(filters/o365): integrate geolocation plugin for origin IP enrichment --- filters/office365/o365.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/filters/office365/o365.yml b/filters/office365/o365.yml index c829fa0d4..f777a27b1 100755 --- a/filters/office365/o365.yml +++ b/filters/office365/o365.yml @@ -1,4 +1,4 @@ -# Microsoft 365 filter, version 1.0.3 +# Microsoft 365 filter, version 1.0.4 # Based on Official documentation # See https://learn.microsoft.com/en-us/compliance/assurance/assurance-microsoft-365-audit-log-collection @@ -92,6 +92,14 @@ pipeline: - log.DestFolder.Path to: log.destFolderPath + # Adding geolocation to origin ip + - dynamic: + plugin: com.utmstack.geolocation + params: + source: origin.ip + destination: origin.geolocation + where: exists("origin.ip") + # Drop unnecessary events - drop: where: oneOf("action", ['SupervisionRuleMatch', 'SupervisoryReviewTag', 'ComplianceManagerAutomationChange', 'LabelContentExplorerAccessedItem', 'CreateCopilotPlugin', 'CreateCopilotPromptBook', 'DeleteCopilotPlugin', 'DeleteCopilotPromptBook', 'DisableCopilotPlugin', 'DisableCopilotPromptBook', 'EnableCopilotPlugin', 'EnableCopilotPromptBook', 'CopilotInteraction', 'UpdateCopilotPlugin', 'UpdateCopilotPromptBook', 'UpdateCopilotSettings', 'ApproveDisposal', 'ExtendRetention', 'RelabelItem', 'SearchUpdated', 'CaseUpdated', 'SearchPermissionUpdated', 'HoldUpdated', 'PreviewItemDownloaded', 'PreviewItemListed', 'SearchCreated', 'CaseAdded', 'HoldCreated', 'SearchRemoved', 'HoldRemoved', 'SearchExportDownloaded', 'SearchPreviewed', 'SearchResultsPurged', 'RemovedSearchResultsSentToZoom', 'RemovedSearchExported', 'RemovedSearchPreviewed', 'RemovedSearchResultsPurged', 'SearchReportRemoved', 'SearchResultsSentToZoom', 'SearchStarted', 'SearchExported', 'SearchReport', 'SearchStopped', 'SearchViewed', 'ViewedSearchExported', 'ViewedSearchPreviewed', 'AddRemediatedData', 'BurnJob', 'CreateWorkingSet', 'CreateWorkingSetSearch', 'CreateTag', 'DeleteWorkingSetSearch', 'DeleteTag', 'DownloadDocument', 'UpdateTag', 'ExportJob', 'UpdateWorkingSetSearch', 'PreviewWorkingSetSearch', 'ErrorRemediationJob', 'TagFiles', 'TagJob', 'ViewDocument', 'Copy', 'Create', 'ApplyRecordLabel', 'HardDelete', 'Send', 'Update', 'FileAccessed', 'FileAccessedExtended', 'ComplianceSettingChanged', 'LockRecord', 'UnlockRecord', 'FileCheckedIn', 'FileCheckedOut', 'FileCopied', 'FileDeletedFirstStageRecycleBin', 'FileDeletedSecondStageRecycleBin', 'RecordDelete', 'DocumentSensitivityMismatchDetected', 'FileCheckOutDiscarded', 'FileDownloaded', 'FileModifiedExtended', 'FilePreviewed', 'SearchQueryPerformed', 'FileRecycled', 'FolderRecycled', 'FileVersionsAllMinorsRecycled', 'FileVersionsAllRecycled', 'FileVersionRecycled', 'FileRestored', 'FileUploaded', 'PageViewed', 'PageViewedExtended', 'ClientViewSignaled', 'PagePrefetched', 'FolderCopied', 'FolderCreated', 'FolderDeletedFirstStageRecycleBin', 'FolderDeletedSecondStageRecycleBin', 'FolderRestored', 'InformationBarriersInsightsReportCompleted', 'InformationBarriersInsightsReportOneDriveSectionQueried', 'InformationBarriersInsightsReportSchedule', 'InformationBarriersInsightsReportSharePointSectionQueried', 'updateddeviceconfiguration', 'UpdatedPolicyConfigPriority', 'BackupPolicyActivated', 'RestoreTaskActivated', 'BackupItemAdded', 'BackupItemRemoved', 'RestoreTaskCompleted', 'DraftRestoreTaskCreated', 'NewBackupPolicyCreated', 'DraftRestoreTaskDeleted', 'DraftRestoreTaskEdited', 'BackupPolicyPaused', 'GetBackupItem', 'ViewBackupPolicyDetails', 'GetRestoreTaskDetails', 'ListAllBackupPolicies', 'ListAllBackupItemsInPolicies', 'ListAllBackupItemsInTenant', 'ListAllBackupItemsInWorkload', 'GetAllRestoreArtifactsInTask', 'ListAllRestorePoints', 'ListAllRestoreTasks', 'BackupItemRestoreCompleted', 'BackupItemRestoreTriggered', 'SetAdvancedFeatures', 'RunAntiVirusScan', 'LogsCollection', 'TaggingConfigurationUpdated', 'AlertExcelDownloaded', 'RemediationActionAdded', 'RemediationActionUpdated', 'SensorCreated', 'SensorDeploymentAccessKeyReceived', 'SensorDeploymentAccessKeyUpdated', 'SensorActivationMethodConfigurationUpdated', 'DomainControllerCoverageExcelDownloaded', 'MonitoringAlertUpdated', 'ReportDownloaded', 'AlertNotificationsRecipientAdded', 'MonitoringAlertNotificationRecipientAdded', 'WorkspaceCreated', 'AddCommentToIncident.', 'AssignUserToIncident', 'UpdateIncidentStatus', 'AddTagsToIncident', 'RemoveTagsFromIncident', 'CreateComment', 'CreateForm', 'MoveForm', 'ViewForm', 'PreviewForm', 'ExportForm', 'AllowShareFormForCopy', 'DisallowShareFormForCopy', 'AddFormCoauthor', 'RemoveFormCoauthor', 'ViewRuntimeForm', 'CreateResponse', 'UpdateResponse', 'ViewResponses', 'ViewResponse', 'GetSummaryLink', 'DeleteSummaryLink', 'ProInvitation', 'ListForms', 'SubmitResponse', 'ConnectToExcelWorkbook', 'CollectionCreated', 'CollectionUpdated', 'CollectionHardDeleted', 'CollectionSoftDeleted', 'CollectionRenamed', 'MovedFormIntoCollection', 'MovedFormOutofCollection', 'PlanCopied', 'TaskAssigned', 'TaskCompleted', 'PlanListRead', 'TaskListRead', 'ProjectCreated', 'RoadmapCreated', 'RoadmapItemCreated', 'TaskCreated', 'ProjectListAccessed', 'RoadmapAccessed', 'RoadmapItemAccessed', 'TaskAccessed', 'AuditSearchCreated', 'AuditSearchCompleted', 'AuditSearchCancelled', 'AuditSearchExportJobCreated', 'AuditSearchExportJobCompleted', 'AuditSearchExportResultsDownloaded', 'EntityCreated', 'ClassificationAdded', 'ClassificationDefinitionCreated', 'GlossaryTermAssigned', 'GlossaryTermCreated', 'BotAddedToTeam', 'ChannelAdded', 'ConnectorAdded', 'MeetingDetail', 'MeetingParticipantDetail', 'MemberAdded', 'TabAdded', 'SensitivityLabelApplied', 'SensitivityLabelChanged', 'ChatCreated', 'TeamCreated', 'MessageDeleted', 'MessageEditedHasLink', 'MessagesExported', 'RecordingExported', 'TranscriptsExported', 'FailedValidation', 'ChatRetrieved', 'MessageHostedContentsListed', 'PerformedCardAction', 'MessageSent', 'AINotesUpdate', 'LiveNotesUpdate', 'AppPublishedToCatalog', 'MessageRead', 'InviteeResponded', 'ChannelOwnerResponded', 'MessagesListed', 'MessageCreatedHasLink', 'MessageCreatedNotification', 'MessageDeletedNotification', 'MessageUpdatedNotification', 'InviteSent', 'SubscribedToMessages', 'AppUpdatedInCatalog', 'ChatUpdated', 'MessageUpdated', 'TabUpdated', 'AppUpgraded', 'MessageSent', 'ScheduleGroupAdded', 'ShiftAdded', 'TimeOffAdded', 'OpenShiftAdded', 'ScheduleShared', 'ClockedIn', 'ClockedOut', 'BreakEnded', 'TimeClockEntryAdded', 'RequestAdded', 'RequestRespondedTo', 'WorkforceIntegrationAdded', 'OffShiftDialogAccepted', 'CreateUpdateRequest', 'EditUpdateRequest', 'SubmitUpdate', 'ViewUpdate', 'AcceptedSharingLinkOnFolder', 'FolderSharingLinkShared', 'LinkedEntityCreated', 'SubTaskCreated', 'TaskCreated', 'TaskRead', 'TaskListCreated', 'TaskListRead', 'AccessedOdataLink', 'CanceledQuery', 'DeletedResult', 'DownloadedReport', 'ExecutedQuery', 'UploadedOrgData', 'ViewedExplore', 'QuarantineReleaseRequestDeny', 'QuarantinePreview', 'QuarantineReleaseRequest', 'QuarantineViewHeader', 'UpdateUsageReportsPrivacySetting', 'NewAdaptiveScope', 'NewComplianceTag', 'NewRetentionCompliancePolicy', 'RemoveAdaptiveScope', 'RemoveComplianceTag', 'SetRestrictiveRetentionUI', 'ExchangeDataProactivelyPreserved', 'SharePointDataProactivelyPreserved', 'ListCreated', 'ListColumnCreated', 'ListContentTypeCreated', 'ListItemCreated', 'SiteColumnCreated', 'SiteContentTypeCreated', 'ListContentTypeDeleted', 'SiteColumnDeleted', 'SiteContentTypeDeleted', 'ListItemRecycled', 'ListItemRestored', 'ListColumnUpdated', 'ListContentTypeUpdated', 'SiteColumnUpdated', 'SiteContentTypeUpdated', 'SharingInvitationCreated', 'AccessRequestUpdated', 'SharingInvitationUpdated', 'SharingInvitationRevoked', 'AllowedDataLocationAdded', 'SiteGeoMoveCancelled', 'MigrationJobCompleted', 'SiteGeoMoveCompleted', 'SiteCollectionCreated', 'HubSiteOrphanHubDeleted', 'PreviewModeEnabledSet', 'LegacyWorkflowEnabledSet', 'OfficeOnDemandSet', 'PeopleResultsScopeSet', 'NewsFeedEnabledSet', 'HubSiteJoined', 'SiteCollectionQuotaModified', 'HubSiteRegistered', 'SiteGeoMoveScheduled', 'GeoQuotaAllocated', 'SiteAdminChangeRequest', 'ManagedSyncClientAllowed', 'FileSyncDownloadedFull', 'FileSyncUploadedFull', 'DataShareCreated', 'DataShareDeleted', 'GenerateCopyOfLakeData', 'DownloadCopyOfLakeData', 'SoftDeleteSettingsUpdated', 'CloseConversation', 'OpenConversation', 'MessageCreation', 'MessageDeleted', 'FileDownloaded', 'DataExport', 'ThreadAccessFailure', 'MarkedMessageChanged', 'RemoveCuratedTopic', 'UsagePolicyAcceptance', 'AdminThreadMuted', 'AdminThreadUnmuted', 'FileUpdateDescription', 'MessageUpdated', 'FileVisited', 'ThreadViewed', 'PulseSubmit', 'PulseCreate', 'PulseExtendDeadline', 'PulseInvite', 'PulseCancel', 'PulseShareResults', 'PulseCreateDraft', 'PulseDeleteDraft']) From 6acf240eb9c14e1c3c13079c36bfb8eab01aa8fc Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Mon, 16 Mar 2026 16:06:06 -0400 Subject: [PATCH 208/240] changeset[backend](rules): updated croudstrike rules changelog --- .../20260316002_modify_crowdstrike_rules.xml | 23 +++++++++++++++++++ .../crowdstrike/utm_correlation_rules.sql | 17 ++++++++++++++ .../crowdstrike/utm_group_rules_data_type.sql | 17 ++++++++++++++ .../resources/config/liquibase/master.xml | 2 ++ 4 files changed, 59 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260316002_modify_crowdstrike_rules.xml create mode 100644 backend/src/main/resources/config/liquibase/data/20260316/crowdstrike/utm_correlation_rules.sql create mode 100644 backend/src/main/resources/config/liquibase/data/20260316/crowdstrike/utm_group_rules_data_type.sql diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316002_modify_crowdstrike_rules.xml b/backend/src/main/resources/config/liquibase/changelog/20260316002_modify_crowdstrike_rules.xml new file mode 100644 index 000000000..c3c2f38d7 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260316002_modify_crowdstrike_rules.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/data/20260316/crowdstrike/utm_correlation_rules.sql b/backend/src/main/resources/config/liquibase/data/20260316/crowdstrike/utm_correlation_rules.sql new file mode 100644 index 000000000..9a1426999 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260316/crowdstrike/utm_correlation_rules.sql @@ -0,0 +1,17 @@ +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1339, 'CrowdStrike Hunting: Windows Event Log Clearing', 0, 3, 2, 'Defense Evasion', 'Indicator Removal: Clear Windows Event Logs', 'A raw process execution was detected attempting to clear Windows Event Logs. Adversaries use this technique to cover their tracks after compromising a host.', '["https://attack.mitre.org/techniques/T1070/001/"]', e'equals("log.event_simpleName", "ProcessRollup2") && exists("log.CommandLine") && regexMatch("log.CommandLine", "(?i).*(wevtutil\\\\s+cl.*|Clear-EventLog.*|Remove-EventLog.*).*")', '2026-03-16 10:00:00.000000', true, true, 'origin', null, '[]', '["lastEvent.log.ComputerName","lastEvent.log.UserName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1340, 'Suspicious Native Downloaders (LoLBin)', 2, 2, 0, 'Command and Control', 'Ingress Tool Transfer', 'Execution of native binaries like certutil, bitsadmin, curl, or wget was detected making external connections, potentially indicating Ingress Tool Transfer by an adversary.', '["https://attack.mitre.org/techniques/T1105/"]', e'equals("log.event_simpleName", "ProcessRollup2") && exists("log.CommandLine") && regexMatch("log.CommandLine", "(?i).*(certutil.*-urlcache|bitsadmin.*-transfer|curl.*http|wget.*http).*")', '2026-03-16 10:00:00.000000', true, true, 'origin', null, '[]', '["lastEvent.log.ComputerName","lastEvent.log.CommandLine"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1341, 'Suspicious Encoded PowerShell Execution', 3, 2, 1, 'Execution', 'Command and Scripting Interpreter: PowerShell', 'A PowerShell process was spawned with arguments indicating base64 encoded commands (-enc, -EncodedCommand). Malware and threat actors often use this to evade string-based detection.', '["https://attack.mitre.org/techniques/T1059/001/"]', e'equals("log.event_simpleName", "ProcessRollup2") && exists("log.CommandLine") && regexMatch("log.CommandLine", "(?i).*(powershell|pwsh).*-(e|en|enc|encodedcommand|ec)\\\\s+.*")', '2026-03-16 10:00:00.000000', true, true, 'origin', null, '[]', '["lastEvent.log.ComputerName","lastEvent.log.CommandLine"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1342, 'Suspicious Downloader Execution (Linux/macOS)', 2, 2, 0, 'Command and Control', 'Ingress Tool Transfer', 'Execution of native downloaders like curl or wget was detected on a Linux or macOS endpoint making HTTP connections, potentially indicating Ingress Tool Transfer by an adversary.', '["https://attack.mitre.org/techniques/T1105/"]', e'exists("log.event_platform") && oneOf("log.event_platform", ["Mac", "Lin"]) && exists("log.event.CommandLine") && regexMatch("log.event.CommandLine", "(?i).*(curl|wget).*http.*")', '2026-03-16 10:00:00.000000', true, true, 'origin', null, '[]', '["lastEvent.log.event.ComputerName","lastEvent.log.event.CommandLine"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1343, 'Security Policy Disabled or Deleted', 1, 3, 3, 'Defense Evasion', 'Impair Defenses: Disable or Modify Tools', 'An administrator or actor has disabled or deleted a security prevention policy in Falcon, which may leave endpoints vulnerable.', '["https://attack.mitre.org/techniques/T1562/001/"]', e'exists("log.eventOperationName") && oneOf("log.eventOperationName", ["disable_policy", "delete_policy", "remove_policy"])', '2026-03-02 23:03:22.378920', true, true, 'origin', null, '[]', '["lastEvent.log.eventUserId","lastEvent.log.eventOperationName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1344, 'Security Defenses Impaired or Policy Disabled', 0, 3, 3, 'Defense Evasion', 'Impair Defenses: Disable or Modify Tools', 'An action was taken on the endpoint that resulted in a critical sensor process or security policy being disabled locally. This strongly indicates defense evasion tampering.', '["https://attack.mitre.org/techniques/T1562/001/"]', e'equals("event.PatternDispositionFlags.PolicyDisabled", true) || oneOf("event.PatternDispositionValue", [8192, 8208, 8320, 8704, 9216, 10240, 12304, 73728, 73744])', '2026-03-16 10:00:00.000000', true, true, 'origin', null, '[]', '["lastEvent.log.event.ComputerName","lastEvent.log.event.PatternDispositionDescription"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1345, 'Real-Time Response (RTR) Session Execution', 3, 3, 1, 'Execution', 'Remote Services', 'A user or API has initiated a remote response (RTR) session on an endpoint. This grants deep access to the host.', '["https://attack.mitre.org/techniques/T1021/"]', e'equals("log.metadataEventType", "RemoteResponseSessionStartEvent")', '2026-03-02 23:03:24.925716', true, true, 'origin', null, '[]', '["lastEvent.log.metadataEventType","lastEvent.log.eventUserId"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1346, 'OS Credential Dumping Activity', 3, 1, 0, 'Credential Access', 'OS Credential Dumping: LSASS Memory', 'The endpoint agent detected activity commonly associated with OS Credential Dumping. This includes attempts to read or dump LSASS memory using known tools.', '["https://attack.mitre.org/techniques/T1003/001/"]', e'equals("log.event_simpleName", "ProcessRollup2") && exists("log.CommandLine") && regexMatch("log.CommandLine", "(?i).*(procdump.*lsass|mimikatz|sekurlsa|lsass\\\\.dmp).*")', '2026-03-16 10:00:00.000000', true, true, 'origin', null, '[]', '["lastEvent.log.ComputerName","lastEvent.log.UserName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1347, 'Multiple Authentication Failures (Possible Brute Force Attack)', 3, 0, 0, 'Credential Access', 'Brute Force: Password Guessing', 'A user or IP address has failed multiple authentication attempts on the CrowdStrike Falcon console within a short period of time.', '["https://attack.mitre.org/techniques/T1110/001/"]', e'equals("log.metadataEventType", "AuthActivityAuditEvent") && equals("log.eventSuccess", false) && exists("origin.ip")', '2026-03-02 23:03:27.493691', true, true, 'origin', '["origin.ip"]', '[{"indexPattern":"v11-log-crowdstrike","with":[{"field":"origin.ip","operator":"filter_term","value":"{{.origin.ip}}"},{"field":"log.eventSuccess","operator":"filter_term","value":"false"}],"or":null,"within":"now-15m","count":5}]', null); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1348, 'Major Incident Generated (CrowdScore)', 3, 3, 3, 'Lateral Movement', 'Lateral Tool Transfer', 'The CrowdScore engine has consolidated multiple detections into a critical incident, a possible indicator of Lateral Movement or widespread intrusion.', '["https://attack.mitre.org/techniques/T1570/","https://attack.mitre.org/tactics/TA0008/"]', e'equals("log.metadataEventType", "IncidentSummaryEvent")', '2026-03-02 23:03:28.702741', true, true, 'origin', null, '[]', '["lastEvent.log.metadataEventType"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1349, 'IP Whitelisting Modification', 1, 3, 2, 'Defense Evasion', 'Impair Defenses: Disable or Modify Cloud Firewall', 'IP addresses have been added to or removed from the CrowdStrike whitelist. An attacker could use this to evade network blocking.', '["https://attack.mitre.org/techniques/T1562/007/"]', e'exists("log.eventOperationName") && oneOf("log.eventOperationName", ["ip_rules_added", "ip_rules_removed"])', '2026-03-02 23:03:30.070795', true, true, 'origin', null, '[]', '["lastEvent.log.eventUserId","lastEvent.log.eventOperationName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1350, 'Inhibit System Recovery (Shadow Copy Deletion)', 0, 3, 3, 'Impact', 'Inhibit System Recovery', 'The Falcon agent detected command line activity attempting to delete Volume Shadow Copies or disable recovery options. This is a highly reliable precursor to Ransomware encryption.', '["https://attack.mitre.org/techniques/T1490/"]', e'equals("log.event_simpleName", "ProcessRollup2") && exists("log.CommandLine") && regexMatch("log.CommandLine", "(?i).*(vssadmin.*delete shadows|wmic.*shadowcopy.*delete|bcdedit.*recoveryenabled.*no).*")', '2026-03-16 10:00:00.000000', true, true, 'origin', null, '[]', '["lastEvent.log.ComputerName","lastEvent.log.UserName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1351, 'Endpoint or XDR Detection Alert', 3, 3, 2, 'Threat Detection', 'Command and Scripting Interpreter', 'A critical detection summary has been generated from Falcon EPP or XDR indicating malicious activity or attack patterns.', '["https://attack.mitre.org/techniques/T1059/"]', e'exists("log.metadataEventType") && oneOf("log.metadataEventType", ["EppDetectionSummaryEvent", "XdrDetectionSummaryEvent"])', '2026-03-02 23:03:32.606143', true, true, 'origin', null, '[]', '["lastEvent.log.metadataEventType"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1352, 'Endpoint Network Containment Action', 0, 2, 3, 'Impact', 'Account Access Removal', 'Containment of a host on the network has been requested, or a previously applied containment has been lifted.', '["https://attack.mitre.org/techniques/T1531/"]', e'exists("log.eventOperationName") && oneOf("log.eventOperationName", ["containment_requested", "lift_containment_requested"])', '2026-03-02 23:03:33.875437', true, true, 'origin', null, '[]', '["lastEvent.log.eventUserId","lastEvent.log.eventOperationName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1353, 'Deletion or Deactivation of User Account', 0, 3, 3, 'Account Manipulation', 'Account Manipulation', 'An administrator has deactivated or deleted a user account in the Falcon console. This indicates account manipulation.', '["https://attack.mitre.org/techniques/T1098/"]', e'exists("log.eventOperationName") && oneOf("log.eventOperationName", ["deactivateUser", "deleteUser"])', '2026-03-02 23:03:35.273093', true, true, 'origin', null, '[]', '["lastEvent.log.eventUserId","lastEvent.log.eventOperationName"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1354, 'Custom Indicator of Compromise (IoC) Detected', 3, 3, 1, 'Threat Detection', 'User Execution', 'The sensor has detected activity that matches an IoC (Hash, Domain, IP) supplied and entered by the client.', '["https://attack.mitre.org/techniques/T1204/"]', e'equals("log.metadataEventType", "CustomerIOCEvent")', '2026-03-02 23:03:36.627153', true, true, 'origin', null, '[]', '["lastEvent.log.metadataEventType"]'); +INSERT INTO public.utm_correlation_rules (id, rule_name, rule_confidentiality, rule_integrity, rule_availability, rule_category, rule_technique, rule_description, rule_references_def, rule_definition_def, rule_last_update, rule_active, system_owner, rule_adversary, rule_deduplicate_by_def, rule_after_events_def, rule_group_by_def) VALUES (1355, 'Critical Role Modification (Privilege Escalation)', 3, 3, 1, 'Privilege Escalation', 'Account Manipulation: Additional Cloud Roles', 'New roles have been granted or updated for a user within the CrowdStrike administration console.', '["https://attack.mitre.org/techniques/T1098/003/"]', e'exists("log.eventOperationName") && oneOf("log.eventOperationName", ["grantUserRoles", "updateUserRoles"])', '2026-03-02 23:03:38.226516', true, true, 'origin', null, '[]', '["lastEvent.log.eventUserId","lastEvent.log.eventOperationName"]'); diff --git a/backend/src/main/resources/config/liquibase/data/20260316/crowdstrike/utm_group_rules_data_type.sql b/backend/src/main/resources/config/liquibase/data/20260316/crowdstrike/utm_group_rules_data_type.sql new file mode 100644 index 000000000..ed6c158dc --- /dev/null +++ b/backend/src/main/resources/config/liquibase/data/20260316/crowdstrike/utm_group_rules_data_type.sql @@ -0,0 +1,17 @@ +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1339, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1340, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1341, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1342, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1343, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1344, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1345, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1346, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1347, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1348, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1349, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1350, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1351, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1352, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1353, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1354, 51, '2026-03-16 10:00:00.000000'); +INSERT INTO public.utm_group_rules_data_type (rule_id, data_type_id, last_update) VALUES (1355, 51, '2026-03-16 10:00:00.000000'); diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index a04f2f66b..74e13df01 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -513,5 +513,7 @@ + + From b4a36a6caf8c1fee940727afd72e5ecde1515392 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Mon, 16 Mar 2026 16:27:57 -0400 Subject: [PATCH 209/240] changeset[backend](filters): updated o365, crowdstrike, system_linux and azure filters --- .../20260316003_update_filter_azure.xml | 27 +++++++++++++++++++ .../20260316004_update_filter_crowdstrike.xml | 27 +++++++++++++++++++ .../20260316005_update_filter_linux.xml | 27 +++++++++++++++++++ .../20260316006_update_filter_o365.xml | 27 +++++++++++++++++++ .../resources/config/liquibase/master.xml | 8 ++++++ 5 files changed, 116 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260316004_update_filter_crowdstrike.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260316005_update_filter_linux.xml create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260316006_update_filter_o365.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml b/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml new file mode 100644 index 000000000..c1fa63f61 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316004_update_filter_crowdstrike.xml b/backend/src/main/resources/config/liquibase/changelog/20260316004_update_filter_crowdstrike.xml new file mode 100644 index 000000000..430a80112 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260316004_update_filter_crowdstrike.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316005_update_filter_linux.xml b/backend/src/main/resources/config/liquibase/changelog/20260316005_update_filter_linux.xml new file mode 100644 index 000000000..acce54f2a --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260316005_update_filter_linux.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316006_update_filter_o365.xml b/backend/src/main/resources/config/liquibase/changelog/20260316006_update_filter_o365.xml new file mode 100644 index 000000000..925f13dc1 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260316006_update_filter_o365.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 74e13df01..780c07554 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -515,5 +515,13 @@ + + + + + + + + From a8b3abf76d3886def4e24c7b4cde0bd193662aaa Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Mon, 16 Mar 2026 16:52:23 -0400 Subject: [PATCH 210/240] fix[backend](filters): fixed azure filter definition --- .../20260316003_update_filter_azure.xml | 909 +++++++++++++++++- 1 file changed, 904 insertions(+), 5 deletions(-) diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml b/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml index c1fa63f61..a23dedcef 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml @@ -8,18 +8,917 @@ From 91fcaa1b857a15b0a0d47a59cd8bf05c0e297cbc Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Mon, 16 Mar 2026 16:53:26 -0400 Subject: [PATCH 211/240] fix[backend](filters): fixed crowdstrike filter definition --- .../20260316004_update_filter_crowdstrike.xml | 718 +++++++++++++++++- 1 file changed, 713 insertions(+), 5 deletions(-) diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316004_update_filter_crowdstrike.xml b/backend/src/main/resources/config/liquibase/changelog/20260316004_update_filter_crowdstrike.xml index 430a80112..65aef5634 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20260316004_update_filter_crowdstrike.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20260316004_update_filter_crowdstrike.xml @@ -8,18 +8,726 @@ From bf16b1ee222b86dd189050522eb5f4a2afe3e5b3 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Mon, 16 Mar 2026 16:58:43 -0400 Subject: [PATCH 212/240] fix[backend](filters): fixed filebeat linux filter --- .../20260316005_update_filter_linux.xml | 391 +++++++++++++++++- 1 file changed, 387 insertions(+), 4 deletions(-) diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316005_update_filter_linux.xml b/backend/src/main/resources/config/liquibase/changelog/20260316005_update_filter_linux.xml index acce54f2a..cd999fd11 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20260316005_update_filter_linux.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20260316005_update_filter_linux.xml @@ -8,17 +8,400 @@ From 1b15f8e4835c007b176ba1e958a05ad54cd679b3 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Mon, 16 Mar 2026 16:59:47 -0400 Subject: [PATCH 213/240] fix[backend](filters): fixed o365 linux filter --- .../20260316006_update_filter_o365.xml | 107 +++++++++++++++++- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316006_update_filter_o365.xml b/backend/src/main/resources/config/liquibase/changelog/20260316006_update_filter_o365.xml index 925f13dc1..4f0c6488a 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20260316006_update_filter_o365.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20260316006_update_filter_o365.xml @@ -10,15 +10,116 @@ UPDATE public.utm_logstash_filter SET filter_version = '1.0.4', updated_at = now(), - logstash_filter = $$# Microsoft 365 filter, version 1.0.4 -# Documentations: filters/office365/o365.yml + logstash_filter = $$# Microsoft 365 filter, version 1.0.4 + +# Based on Official documentation +# See https://learn.microsoft.com/en-us/compliance/assurance/assurance-microsoft-365-audit-log-collection +# https://learn.microsoft.com/es-es/office/office-365-management-api/aip-unified-audit-logs-best-practices +# https://learn.microsoft.com/en-us/purview/audit-log-activities + pipeline: - dataTypes: - o365 steps: - json: source: raw - # ... (updated filter logic) + + - rename: + from: + - log.AppAccessContext.AADSessionId + to: log.appAccessContextAADSessionId + + - rename: + from: + - log.AppAccessContext.APIId + to: log.appAccessContextAPIId + + - rename: + from: + - log.AppAccessContext.ClientAppId + to: log.appAccessContextClientAppId + + - rename: + from: + - log.AppAccessContext.CorrelationId + to: log.appAccessContextCorrelationId + + - rename: + from: + - log.AppAccessContext.IssuedAtTime + to: log.deviceTime + + - rename: + from: + - log.AppAccessContext.UniqueTokenId + to: log.appAccessContextUniqueTokenId + + - rename: + from: + - log.ClientIPAddress + to: origin.ip + + - rename: + from: + - log.Operation + to: action + + - rename: + from: + - log.Version + to: log.version + + - rename: + from: + - log.ClientIP + to: log.clientIP + + - rename: + from: + - log.ResultStatus + to: actionResult + + - rename: + from: + - log.UserId + to: origin.user + + - rename: + from: + - log.Folder.Path + to: log.folderPath + + - rename: + from: + - log.Folder.Id + to: log.folderId + + - rename: + from: + - log.DestFolder.Id + to: log.destFolderId + + - rename: + from: + - log.DestFolder.Path + to: log.destFolderPath + + # Adding geolocation to origin ip + - dynamic: + plugin: com.utmstack.geolocation + params: + source: origin.ip + destination: origin.geolocation + where: exists("origin.ip") + + # Drop unnecessary events + - drop: + where: oneOf("action", ['SupervisionRuleMatch', 'SupervisoryReviewTag', 'ComplianceManagerAutomationChange', 'LabelContentExplorerAccessedItem', 'CreateCopilotPlugin', 'CreateCopilotPromptBook', 'DeleteCopilotPlugin', 'DeleteCopilotPromptBook', 'DisableCopilotPlugin', 'DisableCopilotPromptBook', 'EnableCopilotPlugin', 'EnableCopilotPromptBook', 'CopilotInteraction', 'UpdateCopilotPlugin', 'UpdateCopilotPromptBook', 'UpdateCopilotSettings', 'ApproveDisposal', 'ExtendRetention', 'RelabelItem', 'SearchUpdated', 'CaseUpdated', 'SearchPermissionUpdated', 'HoldUpdated', 'PreviewItemDownloaded', 'PreviewItemListed', 'SearchCreated', 'CaseAdded', 'HoldCreated', 'SearchRemoved', 'HoldRemoved', 'SearchExportDownloaded', 'SearchPreviewed', 'SearchResultsPurged', 'RemovedSearchResultsSentToZoom', 'RemovedSearchExported', 'RemovedSearchPreviewed', 'RemovedSearchResultsPurged', 'SearchReportRemoved', 'SearchResultsSentToZoom', 'SearchStarted', 'SearchExported', 'SearchReport', 'SearchStopped', 'SearchViewed', 'ViewedSearchExported', 'ViewedSearchPreviewed', 'AddRemediatedData', 'BurnJob', 'CreateWorkingSet', 'CreateWorkingSetSearch', 'CreateTag', 'DeleteWorkingSetSearch', 'DeleteTag', 'DownloadDocument', 'UpdateTag', 'ExportJob', 'UpdateWorkingSetSearch', 'PreviewWorkingSetSearch', 'ErrorRemediationJob', 'TagFiles', 'TagJob', 'ViewDocument', 'Copy', 'Create', 'ApplyRecordLabel', 'HardDelete', 'Send', 'Update', 'FileAccessed', 'FileAccessedExtended', 'ComplianceSettingChanged', 'LockRecord', 'UnlockRecord', 'FileCheckedIn', 'FileCheckedOut', 'FileCopied', 'FileDeletedFirstStageRecycleBin', 'FileDeletedSecondStageRecycleBin', 'RecordDelete', 'DocumentSensitivityMismatchDetected', 'FileCheckOutDiscarded', 'FileDownloaded', 'FileModifiedExtended', 'FilePreviewed', 'SearchQueryPerformed', 'FileRecycled', 'FolderRecycled', 'FileVersionsAllMinorsRecycled', 'FileVersionsAllRecycled', 'FileVersionRecycled', 'FileRestored', 'FileUploaded', 'PageViewed', 'PageViewedExtended', 'ClientViewSignaled', 'PagePrefetched', 'FolderCopied', 'FolderCreated', 'FolderDeletedFirstStageRecycleBin', 'FolderDeletedSecondStageRecycleBin', 'FolderRestored', 'InformationBarriersInsightsReportCompleted', 'InformationBarriersInsightsReportOneDriveSectionQueried', 'InformationBarriersInsightsReportSchedule', 'InformationBarriersInsightsReportSharePointSectionQueried', 'updateddeviceconfiguration', 'UpdatedPolicyConfigPriority', 'BackupPolicyActivated', 'RestoreTaskActivated', 'BackupItemAdded', 'BackupItemRemoved', 'RestoreTaskCompleted', 'DraftRestoreTaskCreated', 'NewBackupPolicyCreated', 'DraftRestoreTaskDeleted', 'DraftRestoreTaskEdited', 'BackupPolicyPaused', 'GetBackupItem', 'ViewBackupPolicyDetails', 'GetRestoreTaskDetails', 'ListAllBackupPolicies', 'ListAllBackupItemsInPolicies', 'ListAllBackupItemsInTenant', 'ListAllBackupItemsInWorkload', 'GetAllRestoreArtifactsInTask', 'ListAllRestorePoints', 'ListAllRestoreTasks', 'BackupItemRestoreCompleted', 'BackupItemRestoreTriggered', 'SetAdvancedFeatures', 'RunAntiVirusScan', 'LogsCollection', 'TaggingConfigurationUpdated', 'AlertExcelDownloaded', 'RemediationActionAdded', 'RemediationActionUpdated', 'SensorCreated', 'SensorDeploymentAccessKeyReceived', 'SensorDeploymentAccessKeyUpdated', 'SensorActivationMethodConfigurationUpdated', 'DomainControllerCoverageExcelDownloaded', 'MonitoringAlertUpdated', 'ReportDownloaded', 'AlertNotificationsRecipientAdded', 'MonitoringAlertNotificationRecipientAdded', 'WorkspaceCreated', 'AddCommentToIncident.', 'AssignUserToIncident', 'UpdateIncidentStatus', 'AddTagsToIncident', 'RemoveTagsFromIncident', 'CreateComment', 'CreateForm', 'MoveForm', 'ViewForm', 'PreviewForm', 'ExportForm', 'AllowShareFormForCopy', 'DisallowShareFormForCopy', 'AddFormCoauthor', 'RemoveFormCoauthor', 'ViewRuntimeForm', 'CreateResponse', 'UpdateResponse', 'ViewResponses', 'ViewResponse', 'GetSummaryLink', 'DeleteSummaryLink', 'ProInvitation', 'ListForms', 'SubmitResponse', 'ConnectToExcelWorkbook', 'CollectionCreated', 'CollectionUpdated', 'CollectionHardDeleted', 'CollectionSoftDeleted', 'CollectionRenamed', 'MovedFormIntoCollection', 'MovedFormOutofCollection', 'PlanCopied', 'TaskAssigned', 'TaskCompleted', 'PlanListRead', 'TaskListRead', 'ProjectCreated', 'RoadmapCreated', 'RoadmapItemCreated', 'TaskCreated', 'ProjectListAccessed', 'RoadmapAccessed', 'RoadmapItemAccessed', 'TaskAccessed', 'AuditSearchCreated', 'AuditSearchCompleted', 'AuditSearchCancelled', 'AuditSearchExportJobCreated', 'AuditSearchExportJobCompleted', 'AuditSearchExportResultsDownloaded', 'EntityCreated', 'ClassificationAdded', 'ClassificationDefinitionCreated', 'GlossaryTermAssigned', 'GlossaryTermCreated', 'BotAddedToTeam', 'ChannelAdded', 'ConnectorAdded', 'MeetingDetail', 'MeetingParticipantDetail', 'MemberAdded', 'TabAdded', 'SensitivityLabelApplied', 'SensitivityLabelChanged', 'ChatCreated', 'TeamCreated', 'MessageDeleted', 'MessageEditedHasLink', 'MessagesExported', 'RecordingExported', 'TranscriptsExported', 'FailedValidation', 'ChatRetrieved', 'MessageHostedContentsListed', 'PerformedCardAction', 'MessageSent', 'AINotesUpdate', 'LiveNotesUpdate', 'AppPublishedToCatalog', 'MessageRead', 'InviteeResponded', 'ChannelOwnerResponded', 'MessagesListed', 'MessageCreatedHasLink', 'MessageCreatedNotification', 'MessageDeletedNotification', 'MessageUpdatedNotification', 'InviteSent', 'SubscribedToMessages', 'AppUpdatedInCatalog', 'ChatUpdated', 'MessageUpdated', 'TabUpdated', 'AppUpgraded', 'MessageSent', 'ScheduleGroupAdded', 'ShiftAdded', 'TimeOffAdded', 'OpenShiftAdded', 'ScheduleShared', 'ClockedIn', 'ClockedOut', 'BreakEnded', 'TimeClockEntryAdded', 'RequestAdded', 'RequestRespondedTo', 'WorkforceIntegrationAdded', 'OffShiftDialogAccepted', 'CreateUpdateRequest', 'EditUpdateRequest', 'SubmitUpdate', 'ViewUpdate', 'AcceptedSharingLinkOnFolder', 'FolderSharingLinkShared', 'LinkedEntityCreated', 'SubTaskCreated', 'TaskCreated', 'TaskRead', 'TaskListCreated', 'TaskListRead', 'AccessedOdataLink', 'CanceledQuery', 'DeletedResult', 'DownloadedReport', 'ExecutedQuery', 'UploadedOrgData', 'ViewedExplore', 'QuarantineReleaseRequestDeny', 'QuarantinePreview', 'QuarantineReleaseRequest', 'QuarantineViewHeader', 'UpdateUsageReportsPrivacySetting', 'NewAdaptiveScope', 'NewComplianceTag', 'NewRetentionCompliancePolicy', 'RemoveAdaptiveScope', 'RemoveComplianceTag', 'SetRestrictiveRetentionUI', 'ExchangeDataProactivelyPreserved', 'SharePointDataProactivelyPreserved', 'ListCreated', 'ListColumnCreated', 'ListContentTypeCreated', 'ListItemCreated', 'SiteColumnCreated', 'SiteContentTypeCreated', 'ListContentTypeDeleted', 'SiteColumnDeleted', 'SiteContentTypeDeleted', 'ListItemRecycled', 'ListItemRestored', 'ListColumnUpdated', 'ListContentTypeUpdated', 'SiteColumnUpdated', 'SiteContentTypeUpdated', 'SharingInvitationCreated', 'AccessRequestUpdated', 'SharingInvitationUpdated', 'SharingInvitationRevoked', 'AllowedDataLocationAdded', 'SiteGeoMoveCancelled', 'MigrationJobCompleted', 'SiteGeoMoveCompleted', 'SiteCollectionCreated', 'HubSiteOrphanHubDeleted', 'PreviewModeEnabledSet', 'LegacyWorkflowEnabledSet', 'OfficeOnDemandSet', 'PeopleResultsScopeSet', 'NewsFeedEnabledSet', 'HubSiteJoined', 'SiteCollectionQuotaModified', 'HubSiteRegistered', 'SiteGeoMoveScheduled', 'GeoQuotaAllocated', 'SiteAdminChangeRequest', 'ManagedSyncClientAllowed', 'FileSyncDownloadedFull', 'FileSyncUploadedFull', 'DataShareCreated', 'DataShareDeleted', 'GenerateCopyOfLakeData', 'DownloadCopyOfLakeData', 'SoftDeleteSettingsUpdated', 'CloseConversation', 'OpenConversation', 'MessageCreation', 'MessageDeleted', 'FileDownloaded', 'DataExport', 'ThreadAccessFailure', 'MarkedMessageChanged', 'RemoveCuratedTopic', 'UsagePolicyAcceptance', 'AdminThreadMuted', 'AdminThreadUnmuted', 'FileUpdateDescription', 'MessageUpdated', 'FileVisited', 'ThreadViewed', 'PulseSubmit', 'PulseCreate', 'PulseExtendDeadline', 'PulseInvite', 'PulseCancel', 'PulseShareResults', 'PulseCreateDraft', 'PulseDeleteDraft']) + + # Removing unused fields + - delete: + fields: + - log.AppAccessContext $$ WHERE module_name = 'O365'; ]]> From 32643264d3a1d481c557de05030005b314693998 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Mon, 16 Mar 2026 17:01:16 -0400 Subject: [PATCH 214/240] changeset[backend](filters): added ibm_as400 filter changeset --- .../changelog/20260316007_update_as400.xml | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260316007_update_as400.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316007_update_as400.xml b/backend/src/main/resources/config/liquibase/changelog/20260316007_update_as400.xml new file mode 100644 index 000000000..c87d02e71 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260316007_update_as400.xml @@ -0,0 +1,107 @@ + + + + + + + + + + From c0f1422e81cc2a73530e155a22ab123587716345 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Mon, 16 Mar 2026 17:36:56 -0400 Subject: [PATCH 215/240] fix[backend](filters): fixed as400 and azure syntax errors --- .../changelog/20260316003_update_filter_azure.xml | 4 ++-- .../changelog/20260316007_update_as400.xml | 10 ++++------ .../src/main/resources/config/liquibase/master.xml | 14 ++++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml b/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml index a23dedcef..b1b1d13e2 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20260316003_update_filter_azure.xml @@ -10,7 +10,7 @@ UPDATE public.utm_logstash_filter SET filter_version = '2.0.5', updated_at = now(), - logstash_filter = '# Azure Envent-Hub filter, version 2.0.5 + logstash_filter = $$# Azure Envent-Hub filter, version 2.0.5 # # Documentations # 1- https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log @@ -918,7 +918,7 @@ pipeline: params: key: actionResult value: 'accepted' - where: (greaterOrEqual("statusCode", 200) && lessOrEqual("statusCode", 299)) || (greaterOrEqual("statusCode", 300) && lessOrEqual("statusCode", 399) && greaterThan("origin.bytesReceived", 0))' + where: (greaterOrEqual("statusCode", 200) && lessOrEqual("statusCode", 299)) || (greaterOrEqual("statusCode", 300) && lessOrEqual("statusCode", 399) && greaterThan("origin.bytesReceived", 0))$$ WHERE module_name = 'AZURE'; ]]> diff --git a/backend/src/main/resources/config/liquibase/changelog/20260316007_update_as400.xml b/backend/src/main/resources/config/liquibase/changelog/20260316007_update_as400.xml index c87d02e71..6c820049b 100644 --- a/backend/src/main/resources/config/liquibase/changelog/20260316007_update_as400.xml +++ b/backend/src/main/resources/config/liquibase/changelog/20260316007_update_as400.xml @@ -4,14 +4,13 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"> - + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 780c07554..2a66f654d 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -38,7 +38,7 @@ - + @@ -140,15 +140,15 @@ - + - + - + - + - + @@ -523,5 +523,7 @@ + + From c7142ee2beb92af86ec1f038d07312148db09aa7 Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Thu, 19 Mar 2026 16:10:16 +0300 Subject: [PATCH 216/240] update windows-events filter --- filters/windows/windows-events.yml | 60 +++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/filters/windows/windows-events.yml b/filters/windows/windows-events.yml index 458bf89e5..c8978bfd3 100644 --- a/filters/windows/windows-events.yml +++ b/filters/windows/windows-events.yml @@ -2282,210 +2282,210 @@ pipeline: params: key: log.accessType value: 'read' - where: equals("log.eeventDataAccessMask", "0x1") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x1") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to read the corresponding file data. For a directory object, the right to read the corresponding directory data.\n For a directory, the right to list the contents of the directory.\n For registry objects, this is, Query key value.' - where: equals("log.eeventDataAccessMask", "0x1") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x1") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write' - where: equals("log.eeventDataAccessMask", "0x2") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x2") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to write data to the file.\n For a directory object, the right to create a file in the directory.\n For registry objects, this is, Set key value.' - where: equals("log.eeventDataAccessMask", "0x2") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x2") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'append' - where: equals("log.eeventDataAccessMask", "0x4") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x4") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to append data to the file. (For local files, write operations will not overwrite existing data if this flag is specified without FILE_WRITE_DATA.)\n For a directory object, the right to create a subdirectory.\n For a named pipe, the right to create a pipe.' - where: equals("log.eeventDataAccessMask", "0x4") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x4") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_extended_attributes' - where: equals("log.eeventDataAccessMask", "0x8") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x8") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read extended file attributes.\n For registry objects, this is, Enumerate sub-keys.' - where: equals("log.eeventDataAccessMask", "0x8") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x8") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_extended_attributes' - where: equals("log.eeventDataAccessMask", "0x10") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x10") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to write extended file attributes.' - where: equals("log.eeventDataAccessMask", "0x10") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x10") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'execute' - where: equals("log.eeventDataAccessMask", "0x20") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x20") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a native code file, the right to execute the file. This access right given to scripts may cause the script to be executable, depending on the script interpreter.\n For a directory, the right to traverse the directory. By default, users are assigned the BYPASS_TRAVERSE_CHECKING privilege, which ignores the FILE_TRAVERSE access right.' - where: equals("log.eeventDataAccessMask", "0x20") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x20") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'delete_child' - where: equals("log.eeventDataAccessMask", "0x40") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x40") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a directory, the right to delete a directory and all the files it contains, including read-only files.' - where: equals("log.eeventDataAccessMask", "0x40") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x40") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_attributes' - where: equals("log.eeventDataAccessMask", "0x80") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x80") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read file attributes.' - where: equals("log.eeventDataAccessMask", "0x80") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x80") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_attributes' - where: equals("log.eeventDataAccessMask", "0x100") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x100") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to write file attributes.' - where: equals("log.eeventDataAccessMask", "0x100") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x100") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'delete' - where: equals("log.eeventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to delete the object.' - where: equals("log.eeventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_control' - where: equals("log.eeventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read information in the security descriptor object, without including the information in the system access control list (SACL).' - where: equals("log.eeventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_dac' - where: equals("log.eeventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to modify the discretionary access control list (DACL) in the security descriptor object.' - where: equals("log.eeventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_owner' - where: equals("log.eeventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to change the owner in the security descriptor object' - where: equals("log.eeventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'synchronize' - where: equals("log.eeventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state. Some object types do not support this access right.' - where: equals("log.eeventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'access_sys_sec' - where: equals("log.eeventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The ACCESS_SYS_SEC access right controls the ability to get or set the SACL in an security descriptor object.' - where: equals("log.eeventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) # Decoding the "eventStatus" field - add: From e1c1ceee99b813db49c4c80e7965e6d5309d3b4d Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Thu, 19 Mar 2026 16:31:20 +0300 Subject: [PATCH 217/240] update windows-events filter --- filters/windows/windows-events.yml | 60 +++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/filters/windows/windows-events.yml b/filters/windows/windows-events.yml index c8978bfd3..7f579deae 100644 --- a/filters/windows/windows-events.yml +++ b/filters/windows/windows-events.yml @@ -2282,210 +2282,210 @@ pipeline: params: key: log.accessType value: 'read' - where: equals("log.eventDataAccessMask", "0x1") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "1") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to read the corresponding file data. For a directory object, the right to read the corresponding directory data.\n For a directory, the right to list the contents of the directory.\n For registry objects, this is, Query key value.' - where: equals("log.eventDataAccessMask", "0x1") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "1") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write' - where: equals("log.eventDataAccessMask", "0x2") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "2") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to write data to the file.\n For a directory object, the right to create a file in the directory.\n For registry objects, this is, Set key value.' - where: equals("log.eventDataAccessMask", "0x2") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "2") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'append' - where: equals("log.eventDataAccessMask", "0x4") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "4") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a file object, the right to append data to the file. (For local files, write operations will not overwrite existing data if this flag is specified without FILE_WRITE_DATA.)\n For a directory object, the right to create a subdirectory.\n For a named pipe, the right to create a pipe.' - where: equals("log.eventDataAccessMask", "0x4") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "4") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_extended_attributes' - where: equals("log.eventDataAccessMask", "0x8") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "8") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read extended file attributes.\n For registry objects, this is, Enumerate sub-keys.' - where: equals("log.eventDataAccessMask", "0x8") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "8") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_extended_attributes' - where: equals("log.eventDataAccessMask", "0x10") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "16") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to write extended file attributes.' - where: equals("log.eventDataAccessMask", "0x10") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "16") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'execute' - where: equals("log.eventDataAccessMask", "0x20") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "32") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a native code file, the right to execute the file. This access right given to scripts may cause the script to be executable, depending on the script interpreter.\n For a directory, the right to traverse the directory. By default, users are assigned the BYPASS_TRAVERSE_CHECKING privilege, which ignores the FILE_TRAVERSE access right.' - where: equals("log.eventDataAccessMask", "0x20") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "32") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'delete_child' - where: equals("log.eventDataAccessMask", "0x40") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "64") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'For a directory, the right to delete a directory and all the files it contains, including read-only files.' - where: equals("log.eventDataAccessMask", "0x40") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "64") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_attributes' - where: equals("log.eventDataAccessMask", "0x80") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "128") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read file attributes.' - where: equals("log.eventDataAccessMask", "0x80") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "128") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_attributes' - where: equals("log.eventDataAccessMask", "0x100") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "256") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to write file attributes.' - where: equals("log.eventDataAccessMask", "0x100") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "256") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'delete' - where: equals("log.eventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "65536") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to delete the object.' - where: equals("log.eventDataAccessMask", "0x10000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "65536") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'read_control' - where: equals("log.eventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "131072") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to read information in the security descriptor object, without including the information in the system access control list (SACL).' - where: equals("log.eventDataAccessMask", "0x20000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "131072") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_dac' - where: equals("log.eventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "262144") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to modify the discretionary access control list (DACL) in the security descriptor object.' - where: equals("log.eventDataAccessMask", "0x40000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "262144") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'write_owner' - where: equals("log.eventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "524288") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to change the owner in the security descriptor object' - where: equals("log.eventDataAccessMask", "0x80000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "524288") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'synchronize' - where: equals("log.eventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "1048576") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state. Some object types do not support this access right.' - where: equals("log.eventDataAccessMask", "0x100000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "1048576") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessType value: 'access_sys_sec' - where: equals("log.eventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "16777216") && equals("log.eventCode", 4663) - add: function: 'string' params: key: log.accessDescription value: 'The ACCESS_SYS_SEC access right controls the ability to get or set the SACL in an security descriptor object.' - where: equals("log.eventDataAccessMask", "0x1000000") && equals("log.eventCode", 4663) + where: equals("log.eventDataAccessMask", "16777216") && equals("log.eventCode", 4663) # Decoding the "eventStatus" field - add: From ee9ac94329336e00e11895fc8aa4a662ed5ded4a Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Thu, 19 Mar 2026 09:58:08 -0400 Subject: [PATCH 218/240] changeset[backend](window rule): removed 'Windows: Execution of Persistent Suspicious Program' --- .../20260319001_remove_run_key_susp_process.xml | 14 ++++++++++++++ .../src/main/resources/config/liquibase/master.xml | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260319001_remove_run_key_susp_process.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260319001_remove_run_key_susp_process.xml b/backend/src/main/resources/config/liquibase/changelog/20260319001_remove_run_key_susp_process.xml new file mode 100644 index 000000000..e2f7aa003 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260319001_remove_run_key_susp_process.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 2a66f654d..02c2e3834 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -525,5 +525,7 @@ + + From 49e3ec06244ccf33894bdf7a0d47d0a7b3c8c376 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Thu, 19 Mar 2026 10:06:05 -0400 Subject: [PATCH 219/240] changeset[backend](windows filter): updated windows filter changeset --- .../20260319002_update_windosw_filter.xml | 2934 +++++++++++++++++ .../resources/config/liquibase/master.xml | 3 + 2 files changed, 2937 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260319002_update_windosw_filter.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260319002_update_windosw_filter.xml b/backend/src/main/resources/config/liquibase/changelog/20260319002_update_windosw_filter.xml new file mode 100644 index 000000000..0918f3862 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260319002_update_windosw_filter.xml @@ -0,0 +1,2934 @@ + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 02c2e3834..446b905be 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -527,5 +527,8 @@ + + + From 1db8ba828a3460a93cf6ab1a10d819683c8e7649 Mon Sep 17 00:00:00 2001 From: Osmany Montero Date: Fri, 20 Mar 2026 14:17:37 +0000 Subject: [PATCH 220/240] Update Go SDK --- plugins/geolocation/go.sum | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/plugins/geolocation/go.sum b/plugins/geolocation/go.sum index ad1333a84..69740a5d4 100644 --- a/plugins/geolocation/go.sum +++ b/plugins/geolocation/go.sum @@ -88,8 +88,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl 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/threatwinds/go-sdk v1.1.14 h1:9XqqGPZvDHHuJ/XkfMsDl3fe7Adfi1fMh/PpQFkUkJU= -github.com/threatwinds/go-sdk v1.1.14/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/threatwinds/go-sdk v1.1.15 h1:LvyaNT78y0whlq9ioR0aRKcRCxt0gX0s90X9z1fF5c4= github.com/threatwinds/go-sdk v1.1.15/go.mod h1:Kfu26gkSZDpNNkPvuQbTAW3dWIQ66pVIrNYW1YBG3Kg= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -113,19 +111,14 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -134,43 +127,25 @@ 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/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y= -golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= 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/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= -google.golang.org/genproto/googleapis/api v0.0.0-20260223185530-2f722ef697dc h1:ULD+ToGXUIU6Pkzr1ARxdyvwfHbelw+agoFDRbLg4TU= -google.golang.org/genproto/googleapis/api v0.0.0-20260223185530-2f722ef697dc/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260223185530-2f722ef697dc h1:51Wupg8spF+5FC6D+iMKbOddFjMckETnNnEiZ+HX37s= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260223185530-2f722ef697dc/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= -google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= -google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= 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= From 7297784b6d95f88188df7390cd16be312784c205 Mon Sep 17 00:00:00 2001 From: JocLRojas Date: Fri, 20 Mar 2026 19:41:15 +0300 Subject: [PATCH 221/240] update windows-events filter --- filters/windows/windows-events.yml | 52 +++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/filters/windows/windows-events.yml b/filters/windows/windows-events.yml index 7f579deae..9f5f109f5 100644 --- a/filters/windows/windows-events.yml +++ b/filters/windows/windows-events.yml @@ -45,11 +45,6 @@ pipeline: - log.data.SubjectUserSid to: log.eventDataSubjectUserSid - - rename: - from: - - log.data.SubjectUserSid - to: log.eventDataSubjectUserSid - - rename: from: - log.data.PrivilegeList @@ -334,6 +329,51 @@ pipeline: - log.execution.ProcessID to: log.executionProcessID + - rename: + from: + - log.data.ObjectType + to: log.eventDataObjectType + + - rename: + from: + - log.data.AccessList + to: log.eventDataAccessList + + - rename: + from: + - log.data.HandleId + to: log.eventDataHandleId + + - rename: + from: + - log.data.ObjectName + to: log.eventDataObjectName + + - rename: + from: + - log.data.ResourceAttributes + to: log.eventDataResourceAttributes + + - rename: + from: + - log.data.OldSd + to: log.eventDataOldSd + + - rename: + from: + - log.data.NewSd + to: log.eventDataNewSd + + - rename: + from: + - log.data.ObjectServer + to: log.eventDataObjectServer + + - rename: + from: + - log.data.TransactionId + to: log.eventDataTransactionId + - cast: to: "int" fields: @@ -348,6 +388,7 @@ pipeline: - log.logonGuid - log.eventDataSchema - log.processThread + - log.eventDataTransactionId - trim: function: suffix @@ -358,6 +399,7 @@ pipeline: - log.logonGuid - log.eventDataSchema - log.processThread + - log.eventDataTransactionId # Drop unnecessary events - drop: From 609acfac68c78716f37250dde27ad613d7ae134c Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Fri, 20 Mar 2026 12:51:25 -0400 Subject: [PATCH 222/240] chanelog[frontend](window_filter): updated windows filter --- .../20260320001_update_window_filter.xml | 2979 +++++++++++++++++ 1 file changed, 2979 insertions(+) create mode 100644 backend/src/main/resources/config/liquibase/changelog/20260320001_update_window_filter.xml diff --git a/backend/src/main/resources/config/liquibase/changelog/20260320001_update_window_filter.xml b/backend/src/main/resources/config/liquibase/changelog/20260320001_update_window_filter.xml new file mode 100644 index 000000000..aaa829405 --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20260320001_update_window_filter.xml @@ -0,0 +1,2979 @@ + + + + + + + + + + + + + From 97f58bf1c4e4f4c24468cc0ad40fb6ddf365e7e6 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Fri, 20 Mar 2026 12:15:00 -0400 Subject: [PATCH 223/240] fix[frontend](file_classification): setted action mask to a decimal integer --- .../shared/const/file-acces-mask.constant.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/data-management/file-management/shared/const/file-acces-mask.constant.ts b/frontend/src/app/data-management/file-management/shared/const/file-acces-mask.constant.ts index 4f7be32f5..ff7570582 100644 --- a/frontend/src/app/data-management/file-management/shared/const/file-acces-mask.constant.ts +++ b/frontend/src/app/data-management/file-management/shared/const/file-acces-mask.constant.ts @@ -4,7 +4,7 @@ import {FileAccessMaskCodeType} from '../types/file-access-mask-code.type'; export const ACCESS_MASK_CODES: FileAccessMaskCodeType[] = [ { access: 'ReadData (or ListDirectory)', - hex: AccessMaskEnum.READ_DATA, + hex: parseInt(AccessMaskEnum.READ_DATA,16), description: 'ReadData - For a file object, the right to read ' + 'the corresponding file data. For a directory object, the right to read the' + ' corresponding directory data.\n' + @@ -12,14 +12,14 @@ export const ACCESS_MASK_CODES: FileAccessMaskCodeType[] = [ }, { access: 'WriteData (or AddFile)', - hex: AccessMaskEnum.WRITE_DATA, + hex:parseInt( AccessMaskEnum.WRITE_DATA,16), description: 'WriteData - For a file object, the right to write data to the file. ' + 'For a directory object, the right to create a file in the directory (FILE_ADD_FILE).\n' + 'AddFile - For a directory, the right to create a file in the directory..' }, { access: 'AppendData (or AddSubdirectory or CreatePipeInstance)', - hex: AccessMaskEnum.APPEND_DATA, + hex:parseInt( AccessMaskEnum.APPEND_DATA,16), description: 'AppendData - For a file object, the right to append data to the file.' + ' (For local files, write operations will not overwrite existing data if this flag ' + 'is specified without FILE_WRITE_DATA.) For a directory object, the right to create a ' + @@ -29,17 +29,17 @@ export const ACCESS_MASK_CODES: FileAccessMaskCodeType[] = [ }, { access: 'ReadEA(For registry objects, this is Enumerate sub-keys.)', - hex: AccessMaskEnum.READ_EA, + hex:parseInt( AccessMaskEnum.READ_EA,16), description: 'The right to read extended file attributes.' }, { access: 'WriteEA', - hex: AccessMaskEnum.WRITE_EA, + hex:parseInt( AccessMaskEnum.WRITE_EA,16), description: 'The right to write extended file attributes.' }, { access: 'Execute/Traverse', - hex: AccessMaskEnum.EXECUTE_TRAVERSE, + hex:parseInt( AccessMaskEnum.EXECUTE_TRAVERSE,16), description: 'Execute - For a native code file, the right to execute' + ' the file. This access right given to scripts may cause the ' + 'script to be executable, depending on the script interpreter.\n' + @@ -50,52 +50,52 @@ export const ACCESS_MASK_CODES: FileAccessMaskCodeType[] = [ }, { access: 'Delete child', - hex: AccessMaskEnum.DELETE_CHILD, + hex:parseInt( AccessMaskEnum.DELETE_CHILD,16), description: 'For a directory, the right to delete a directory and all the ' + 'files it contains, including read-only files.' }, { access: 'Read attributes', - hex: AccessMaskEnum.READ_ATTRIBUTES, + hex:parseInt( AccessMaskEnum.READ_ATTRIBUTES,16), description: 'The right to read file attributes.' }, { access: 'Write attributes', - hex: AccessMaskEnum.WRITE_ATTRIBUTES, + hex:parseInt( AccessMaskEnum.WRITE_ATTRIBUTES,16), description: 'The right to write file attributes.' }, { access: 'Delete', - hex: AccessMaskEnum.DELETE, + hex:parseInt( AccessMaskEnum.DELETE,16), description: 'The right to delete the object.' }, { access: 'Read control', - hex: AccessMaskEnum.READ_CONTROL, + hex:parseInt( AccessMaskEnum.READ_CONTROL,16), description: 'The right to read the information in the object\'s security' + ' descriptor, not including the information' + ' in the system access control list (SACL).' }, { access: 'Write DAC', - hex: AccessMaskEnum.WRITE_AC, + hex:parseInt( AccessMaskEnum.WRITE_AC,16), description: 'The right to modify the discretionary access control list' + ' (DACL) in the object\'s security descriptor.' }, { access: 'Write OWNER', - hex: AccessMaskEnum.WRITE_OWNER, + hex:parseInt( AccessMaskEnum.WRITE_OWNER,16), description: 'The right to change the owner in the object\'s security descriptor' }, { access: 'Synchronize', - hex: AccessMaskEnum.SYNCHRONIZE, + hex:parseInt( AccessMaskEnum.SYNCHRONIZE,16), description: 'The right to use the object for synchronization. This enables a thread to ' + 'wait until the object is in the signaled state. Some object type do not support this access right.' }, { access: 'Access SYS_SEC', - hex: AccessMaskEnum.ACCESS_SYS_SEC, + hex:parseInt( AccessMaskEnum.ACCESS_SYS_SEC,16), description: 'The ACCESS_SYS_SEC access right controls the ability to get or set the SACL' + ' in an object\'s security descriptor.' } From 0d5411c47111c4e5b4fdb2ec39d58411d5a7d4b9 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Fri, 20 Mar 2026 12:15:40 -0400 Subject: [PATCH 224/240] fix[frontend](file_classification): fixed event fields and names to fit filters --- .../shared/const/file-field.constant.ts | 38 +------------------ .../shared/enum/file-field.enum.ts | 2 +- .../types/file-access-mask-code.type.ts | 2 +- 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/frontend/src/app/data-management/file-management/shared/const/file-field.constant.ts b/frontend/src/app/data-management/file-management/shared/const/file-field.constant.ts index f273552d1..88d435794 100644 --- a/frontend/src/app/data-management/file-management/shared/const/file-field.constant.ts +++ b/frontend/src/app/data-management/file-management/shared/const/file-field.constant.ts @@ -94,12 +94,6 @@ export const FILE_FIELDS: UtmFieldType[] = [ type: ElasticDataTypesEnum.STRING, visible: false, }, - { - label: 'Subject domain name', - field: FileFieldEnum.FILE_SUBJECT_DOMAIN_NAME_FIELD, - type: ElasticDataTypesEnum.STRING, - visible: false, - }, { label: 'Subject logon ID', field: FileFieldEnum.FILE_SUBJECT_LOGON_ID_FIELD, @@ -112,12 +106,6 @@ export const FILE_FIELDS: UtmFieldType[] = [ type: ElasticDataTypesEnum.STRING, visible: false, }, - { - label: 'Host architecture', - field: FileFieldEnum.FILE_HOST_ARCHITECTURE_FIELD, - type: ElasticDataTypesEnum.STRING, - visible: false, - }, { label: 'Host ID', field: FileFieldEnum.FILE_HOST_ID_FIELD, @@ -136,30 +124,6 @@ export const FILE_FIELDS: UtmFieldType[] = [ type: ElasticDataTypesEnum.STRING, visible: false, }, - { - label: 'OS Build', - field: FileFieldEnum.FILE_HOTS_OS_BUILD_FIELD, - type: ElasticDataTypesEnum.STRING, - visible: false, - }, - { - label: 'OS Family', - field: FileFieldEnum.FILE_HOST_OS_FAMILY_FIELD, - type: ElasticDataTypesEnum.STRING, - visible: false, - }, - { - label: 'OS Platform', - field: FileFieldEnum.FILE_HOST_OS_PLATFORM_FIELD, - type: ElasticDataTypesEnum.STRING, - visible: false, - }, - { - label: 'OS Version', - field: FileFieldEnum.FILE_HOST_OS_VERSION_FIELD, - type: ElasticDataTypesEnum.STRING, - visible: false, - }, { label: 'Keywords', field: FileFieldEnum.FILE_KEYWORD_FIELD, @@ -833,4 +797,4 @@ export const DELETED_FILE_EVENT_ID_NUMBER = [4663]; export const CREATED_FILE_EVENT_ID_NUMBER = 4663; export const FILE_OBJECT_TYPE_VALUE = ['File', 'Folder']; -// NETWORK SHARE FIELDS + diff --git a/frontend/src/app/data-management/file-management/shared/enum/file-field.enum.ts b/frontend/src/app/data-management/file-management/shared/enum/file-field.enum.ts index 46f200684..5c3fd3477 100644 --- a/frontend/src/app/data-management/file-management/shared/enum/file-field.enum.ts +++ b/frontend/src/app/data-management/file-management/shared/enum/file-field.enum.ts @@ -16,7 +16,7 @@ export enum FileFieldEnum { FILE_EVENT_ID_FIELD = 'log.eventCode', FILE_EVENT_NAME_FIELD = 'log.eventName', FILE_HOST_ARCHITECTURE_FIELD = 'log.cpuArchitecture', - FILE_HOST_ID_FIELD = 'log.id', + FILE_HOST_ID_FIELD = 'id', FILE_HOST_NAME_FIELD = 'origin.host', FILE_HOST_OS_NAME_FIELD = 'log.computer', FILE_MESSAGE_FIELD = 'log.eventName', diff --git a/frontend/src/app/data-management/file-management/shared/types/file-access-mask-code.type.ts b/frontend/src/app/data-management/file-management/shared/types/file-access-mask-code.type.ts index b1db313e0..0d00bbb3b 100644 --- a/frontend/src/app/data-management/file-management/shared/types/file-access-mask-code.type.ts +++ b/frontend/src/app/data-management/file-management/shared/types/file-access-mask-code.type.ts @@ -1,5 +1,5 @@ export class FileAccessMaskCodeType { access: string; - hex?: string; + hex?: number; description?: string; } From 48b97f9d8c3dfd7cbcaa677ff0f9abf3c9a9bbb5 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Fri, 20 Mar 2026 13:34:12 -0400 Subject: [PATCH 225/240] fix[frontend](file_classification): fixed access mask enum type setted to numeric values and matched to hex on filter show --- .../shared/const/file-acces-mask.constant.ts | 30 +++++++++---------- .../shared/enum/access-mask.enum.ts | 30 +++++++++---------- .../types/file-access-mask-code.type.ts | 2 +- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/frontend/src/app/data-management/file-management/shared/const/file-acces-mask.constant.ts b/frontend/src/app/data-management/file-management/shared/const/file-acces-mask.constant.ts index ff7570582..dabdf2b2b 100644 --- a/frontend/src/app/data-management/file-management/shared/const/file-acces-mask.constant.ts +++ b/frontend/src/app/data-management/file-management/shared/const/file-acces-mask.constant.ts @@ -4,7 +4,7 @@ import {FileAccessMaskCodeType} from '../types/file-access-mask-code.type'; export const ACCESS_MASK_CODES: FileAccessMaskCodeType[] = [ { access: 'ReadData (or ListDirectory)', - hex: parseInt(AccessMaskEnum.READ_DATA,16), + hex: `${AccessMaskEnum.READ_DATA}`, description: 'ReadData - For a file object, the right to read ' + 'the corresponding file data. For a directory object, the right to read the' + ' corresponding directory data.\n' + @@ -12,14 +12,14 @@ export const ACCESS_MASK_CODES: FileAccessMaskCodeType[] = [ }, { access: 'WriteData (or AddFile)', - hex:parseInt( AccessMaskEnum.WRITE_DATA,16), + hex:`${ AccessMaskEnum.WRITE_DATA}`, description: 'WriteData - For a file object, the right to write data to the file. ' + 'For a directory object, the right to create a file in the directory (FILE_ADD_FILE).\n' + 'AddFile - For a directory, the right to create a file in the directory..' }, { access: 'AppendData (or AddSubdirectory or CreatePipeInstance)', - hex:parseInt( AccessMaskEnum.APPEND_DATA,16), + hex:`${ AccessMaskEnum.APPEND_DATA}`, description: 'AppendData - For a file object, the right to append data to the file.' + ' (For local files, write operations will not overwrite existing data if this flag ' + 'is specified without FILE_WRITE_DATA.) For a directory object, the right to create a ' + @@ -29,17 +29,17 @@ export const ACCESS_MASK_CODES: FileAccessMaskCodeType[] = [ }, { access: 'ReadEA(For registry objects, this is Enumerate sub-keys.)', - hex:parseInt( AccessMaskEnum.READ_EA,16), + hex:`${ AccessMaskEnum.READ_EA}`, description: 'The right to read extended file attributes.' }, { access: 'WriteEA', - hex:parseInt( AccessMaskEnum.WRITE_EA,16), + hex:`${ AccessMaskEnum.WRITE_EA}`, description: 'The right to write extended file attributes.' }, { access: 'Execute/Traverse', - hex:parseInt( AccessMaskEnum.EXECUTE_TRAVERSE,16), + hex:`${ AccessMaskEnum.EXECUTE_TRAVERSE}`, description: 'Execute - For a native code file, the right to execute' + ' the file. This access right given to scripts may cause the ' + 'script to be executable, depending on the script interpreter.\n' + @@ -50,52 +50,52 @@ export const ACCESS_MASK_CODES: FileAccessMaskCodeType[] = [ }, { access: 'Delete child', - hex:parseInt( AccessMaskEnum.DELETE_CHILD,16), + hex:`${ AccessMaskEnum.DELETE_CHILD}`, description: 'For a directory, the right to delete a directory and all the ' + 'files it contains, including read-only files.' }, { access: 'Read attributes', - hex:parseInt( AccessMaskEnum.READ_ATTRIBUTES,16), + hex:`${ AccessMaskEnum.READ_ATTRIBUTES}`, description: 'The right to read file attributes.' }, { access: 'Write attributes', - hex:parseInt( AccessMaskEnum.WRITE_ATTRIBUTES,16), + hex:`${ AccessMaskEnum.WRITE_ATTRIBUTES}`, description: 'The right to write file attributes.' }, { access: 'Delete', - hex:parseInt( AccessMaskEnum.DELETE,16), + hex:`${ AccessMaskEnum.DELETE}`, description: 'The right to delete the object.' }, { access: 'Read control', - hex:parseInt( AccessMaskEnum.READ_CONTROL,16), + hex:`${ AccessMaskEnum.READ_CONTROL}`, description: 'The right to read the information in the object\'s security' + ' descriptor, not including the information' + ' in the system access control list (SACL).' }, { access: 'Write DAC', - hex:parseInt( AccessMaskEnum.WRITE_AC,16), + hex:`${ AccessMaskEnum.WRITE_AC}`, description: 'The right to modify the discretionary access control list' + ' (DACL) in the object\'s security descriptor.' }, { access: 'Write OWNER', - hex:parseInt( AccessMaskEnum.WRITE_OWNER,16), + hex:`${ AccessMaskEnum.WRITE_OWNER}`, description: 'The right to change the owner in the object\'s security descriptor' }, { access: 'Synchronize', - hex:parseInt( AccessMaskEnum.SYNCHRONIZE,16), + hex:`${ AccessMaskEnum.SYNCHRONIZE}`, description: 'The right to use the object for synchronization. This enables a thread to ' + 'wait until the object is in the signaled state. Some object type do not support this access right.' }, { access: 'Access SYS_SEC', - hex:parseInt( AccessMaskEnum.ACCESS_SYS_SEC,16), + hex:`${ AccessMaskEnum.ACCESS_SYS_SEC}`, description: 'The ACCESS_SYS_SEC access right controls the ability to get or set the SACL' + ' in an object\'s security descriptor.' } diff --git a/frontend/src/app/data-management/file-management/shared/enum/access-mask.enum.ts b/frontend/src/app/data-management/file-management/shared/enum/access-mask.enum.ts index 731e18dcb..4243328f5 100644 --- a/frontend/src/app/data-management/file-management/shared/enum/access-mask.enum.ts +++ b/frontend/src/app/data-management/file-management/shared/enum/access-mask.enum.ts @@ -1,17 +1,17 @@ export enum AccessMaskEnum { - READ_DATA = '0x1', - WRITE_DATA = '0x2', - APPEND_DATA = '0x4', - READ_EA = '0x8', - WRITE_EA = '0x10', - EXECUTE_TRAVERSE = '0x20', - DELETE_CHILD = '0x40', - READ_ATTRIBUTES = '0x80', - WRITE_ATTRIBUTES = '0x100', - DELETE = '0x10000', - READ_CONTROL = '0x20000', - WRITE_AC = '0x40000', - WRITE_OWNER = '0x80000', - SYNCHRONIZE = '0x100000', - ACCESS_SYS_SEC = '0x1000000' + READ_DATA = 0x1, + WRITE_DATA = 0x2, + APPEND_DATA = 0x4, + READ_EA = 0x8, + WRITE_EA = 0x10, + EXECUTE_TRAVERSE = 0x20, + DELETE_CHILD = 0x40, + READ_ATTRIBUTES = 0x80, + WRITE_ATTRIBUTES = 0x100, + DELETE = 0x10000, + READ_CONTROL = 0x20000, + WRITE_AC = 0x40000, + WRITE_OWNER = 0x80000, + SYNCHRONIZE = 0x100000, + ACCESS_SYS_SEC = 0x1000000 } diff --git a/frontend/src/app/data-management/file-management/shared/types/file-access-mask-code.type.ts b/frontend/src/app/data-management/file-management/shared/types/file-access-mask-code.type.ts index 0d00bbb3b..b1db313e0 100644 --- a/frontend/src/app/data-management/file-management/shared/types/file-access-mask-code.type.ts +++ b/frontend/src/app/data-management/file-management/shared/types/file-access-mask-code.type.ts @@ -1,5 +1,5 @@ export class FileAccessMaskCodeType { access: string; - hex?: number; + hex?: string; description?: string; } From 6f4270253da847796a9e2a9e344bb802f1b71f8f Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Fri, 20 Mar 2026 15:14:51 -0400 Subject: [PATCH 226/240] fix[frontend](file_classification): fixed overflow and scroll on tabular view --- .../file-management/file-view/file-view.component.html | 4 +++- .../file-management/file-view/file-view.component.scss | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/data-management/file-management/file-view/file-view.component.html b/frontend/src/app/data-management/file-management/file-view/file-view.component.html index 391c514c6..cde5e54a9 100644 --- a/frontend/src/app/data-management/file-management/file-view/file-view.component.html +++ b/frontend/src/app/data-management/file-management/file-view/file-view.component.html @@ -1,3 +1,4 @@ +
    @@ -12,7 +13,7 @@
    -
    +
    +
    diff --git a/frontend/src/app/data-management/file-management/file-view/file-view.component.scss b/frontend/src/app/data-management/file-management/file-view/file-view.component.scss index e69de29bb..559327a60 100644 --- a/frontend/src/app/data-management/file-management/file-view/file-view.component.scss +++ b/frontend/src/app/data-management/file-management/file-view/file-view.component.scss @@ -0,0 +1,6 @@ +.main-container{ + margin: 0px; + padding: 0px; + max-height: 90dvh; + overflow-y: auto; +} From cc7956513aadae72861784da19cc8e309e17d96d Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Fri, 20 Mar 2026 16:31:21 -0400 Subject: [PATCH 227/240] fix[frontend](file_classification): sync scroll with logexplorer one --- .../file-management/file-view/file-view.component.html | 10 ++++------ .../file-management/file-view/file-view.component.scss | 4 +--- frontend/src/environments/environment.ts | 4 ++-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/frontend/src/app/data-management/file-management/file-view/file-view.component.html b/frontend/src/app/data-management/file-management/file-view/file-view.component.html index cde5e54a9..c1bc2b216 100644 --- a/frontend/src/app/data-management/file-management/file-view/file-view.component.html +++ b/frontend/src/app/data-management/file-management/file-view/file-view.component.html @@ -1,4 +1,3 @@ -
    @@ -13,7 +12,7 @@
    -
    +
    - + class="table-responsive resizable-table-responsive main-container"> +
    - +
    @@ -71,7 +70,7 @@
    @@ -157,4 +156,3 @@
    - diff --git a/frontend/src/app/data-management/file-management/file-view/file-view.component.scss b/frontend/src/app/data-management/file-management/file-view/file-view.component.scss index 559327a60..a63d9574a 100644 --- a/frontend/src/app/data-management/file-management/file-view/file-view.component.scss +++ b/frontend/src/app/data-management/file-management/file-view/file-view.component.scss @@ -1,6 +1,4 @@ .main-container{ - margin: 0px; - padding: 0px; - max-height: 90dvh; + max-height: 70dvh; overflow-y: auto; } diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index 586cb5a3b..d1fff92cc 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -4,8 +4,8 @@ export const environment = { production: false, - // SERVER_API_URL: 'https://192.168.1.18/', - SERVER_API_URL: 'http://localhost:8080/', + SERVER_API_URL: 'https://192.168.1.18/', + // SERVER_API_URL: 'http://localhost:8080/', SERVER_API_CONTEXT: '', SESSION_AUTH_TOKEN: window.location.host.split(':')[0].toLocaleUpperCase(), WEBSOCKET_URL: '//localhost:8080', From 7aa5d4fdf2d8f05c6b37d4ad6a9eefbba19171c0 Mon Sep 17 00:00:00 2001 From: AlexSanchez-bit Date: Mon, 23 Mar 2026 13:01:18 -0400 Subject: [PATCH 228/240] fix[frontend](cisco-switch-int-guide): changed cisco switch int guide command 'cisco' by 'cisco-switch' --- .../app-module/guides/guide-cisco/guide-cisco.component.html | 2 +- .../app-module/guides/guide-syslog/guide-syslog.component.ts | 3 ++- .../module-integration/module-integration.component.html | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/app-module/guides/guide-cisco/guide-cisco.component.html b/frontend/src/app/app-module/guides/guide-cisco/guide-cisco.component.html index 88e3d8b4e..b5168fed0 100644 --- a/frontend/src/app/app-module/guides/guide-cisco/guide-cisco.component.html +++ b/frontend/src/app/app-module/guides/guide-cisco/guide-cisco.component.html @@ -29,7 +29,7 @@

  13. 2 - Enable log collector and this integration in the configuration file which + Enable log collector and this integration in the configuration file which you can find where your UTMStack Agent is located, in the path:

    diff --git a/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts b/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts index 6f032090a..1155ab82b 100644 --- a/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts +++ b/frontend/src/app/app-module/guides/guide-syslog/guide-syslog.component.ts @@ -98,9 +98,10 @@ export class GuideSyslogComponent implements OnInit { switch (this.moduleEnum) { case UtmModulesEnum.FIRE_POWER: case UtmModulesEnum.CISCO: - case UtmModulesEnum.CISCO_SWITCH: case UtmModulesEnum.MERAKI: return 'cisco'; + case UtmModulesEnum.CISCO_SWITCH: + return 'cisco-switch'; default: return this.dataType; } diff --git a/frontend/src/app/app-module/module-integration/module-integration.component.html b/frontend/src/app/app-module/module-integration/module-integration.component.html index 3bde18fc2..b39513881 100644 --- a/frontend/src/app/app-module/module-integration/module-integration.component.html +++ b/frontend/src/app/app-module/module-integration/module-integration.component.html @@ -96,7 +96,7 @@