diff --git a/agent-manager/agent/agent.go b/agent-manager/agent/agent.go index 36ae11767..7a0fbd0d8 100644 --- a/agent-manager/agent/agent.go +++ b/agent-manager/agent/agent.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "log" + "regexp" "time" "github.com/utmstack/UTMStack/agent-manager/config" @@ -101,6 +102,19 @@ func (s *Grpc) AgentStream(stream AgentService_AgentStreamServer) error { } } +func (s *Grpc) replaceSecretValues(input string) string { + pattern := regexp.MustCompile(`\$\[(\w+):([^\]]+)\]`) + return pattern.ReplaceAllStringFunc(input, func(match string) string { + matches := pattern.FindStringSubmatch(match) + if len(matches) < 3 { + return match // In case of no match, return the original + } + encryptedValue := matches[2] + decryptedValue, _ := util.DecryptValue(encryptedValue) + return decryptedValue + }) +} + func (s *Grpc) ProcessCommand(stream PanelService_ProcessCommandServer) error { for { cmd, err := stream.Recv() @@ -144,7 +158,7 @@ func (s *Grpc) ProcessCommand(stream PanelService_ProcessCommandServer) error { StreamMessage: &BidirectionalStream_Command{ Command: &UtmCommand{ AgentKey: cmd.AgentKey, - Command: cmd.Command, + Command: s.replaceSecretValues(cmd.Command), CmdId: cmdID, InternalKey: config.GetInternalKey(), }, diff --git a/agent-manager/config/global_const.go b/agent-manager/config/global_const.go index 0b336f326..dd9be83d7 100644 --- a/agent-manager/config/global_const.go +++ b/agent-manager/config/global_const.go @@ -27,6 +27,7 @@ func ConnectionKeyRoutes() []string { const PanelConnectionKeyUrl = "%s/api/authenticateFederationServiceManager" const UTMSharedKeyEnv = "INTERNAL_KEY" +const UTMEncryptionKeyEnv = "ENCRYPTION_KEY" const UTMHostEnv = "UTM_HOST" func GetInternalKey() string { diff --git a/agent-manager/util/aes.go b/agent-manager/util/aes.go index 39de106f1..7a0a8d201 100644 --- a/agent-manager/util/aes.go +++ b/agent-manager/util/aes.go @@ -10,7 +10,7 @@ import ( ) var ( - passphrase = os.Getenv(config.UTMSharedKeyEnv) + passphrase = os.Getenv(config.UTMEncryptionKeyEnv) ) func EncryptDecryptConfValues(conf *models.AgentModuleConfiguration, action string) *models.AgentModuleConfiguration { 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 b86851b4b..1aa8c037c 100644 --- a/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java @@ -55,8 +55,8 @@ public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerB public void init() { try { authenticationManagerBuilder - .userDetailsService(userDetailsService) - .passwordEncoder(passwordEncoder()); + .userDetailsService(userDetailsService) + .passwordEncoder(passwordEncoder()); } catch (Exception e) { throw new BeanInitializationException("Security configuration failed", e); } @@ -76,51 +76,53 @@ public PasswordEncoder passwordEncoder() { @Bean public WebSecurityCustomizer webSecurityCustomizer() { return web -> web.ignoring() - .antMatchers(HttpMethod.OPTIONS, "/**") - .antMatchers("/swagger-ui/**") - .antMatchers("/i18n/**"); + .antMatchers(HttpMethod.OPTIONS, "/**") + .antMatchers("/swagger-ui/**") + .antMatchers("/i18n/**"); } @Override public void configure(HttpSecurity http) throws Exception { http - .csrf() - .disable() - .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) - .exceptionHandling() - .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)) - .accessDeniedHandler((request, response, accessDeniedException) -> response.sendError(HttpServletResponse.SC_FORBIDDEN)) - .and() - .headers() - .frameOptions() - .disable() - .and() - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() - .authorizeRequests() - .antMatchers("/api/authenticate").permitAll() - .antMatchers("/api/authenticateFederationServiceManager").permitAll() - .antMatchers("/api/ping").permitAll() - .antMatchers("/api/date-format").permitAll() - .antMatchers("/api/healthcheck").permitAll() - .antMatchers("/api/releaseInfo").permitAll() - .antMatchers("/api/account/reset-password/init").permitAll() - .antMatchers("/api/account/reset-password/finish").permitAll() - .antMatchers("/api/images/all").permitAll() - .antMatchers("/api/tfa/verifyCode").hasAuthority(AuthoritiesConstants.PRE_VERIFICATION_USER) - .antMatchers("/api/utm-incident-jobs").hasAuthority(AuthoritiesConstants.ADMIN) - .antMatchers("/api/utm-incident-jobs/**").hasAuthority(AuthoritiesConstants.ADMIN) - .antMatchers("/api/custom-reports/**").denyAll() - .antMatchers("/api/**").hasAnyAuthority(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER) - .antMatchers("/ws/topic").hasAuthority(AuthoritiesConstants.ADMIN) - .antMatchers("/ws/**").permitAll() - .antMatchers("/management/info").permitAll() - .antMatchers("/management/**").hasAnyAuthority(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER) - .and() - .apply(securityConfigurerAdapterForJwt()) - .and() - .apply(securityConfigurerAdapterForInternalApiKey()); + .csrf() + .disable() + .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) + .exceptionHandling() + .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)) + .accessDeniedHandler((request, response, accessDeniedException) -> response.sendError(HttpServletResponse.SC_FORBIDDEN)) + .and() + .headers() + .frameOptions() + .disable() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests() + .antMatchers("/api/authenticate").permitAll() + .antMatchers("/api/authenticateFederationServiceManager").permitAll() + .antMatchers("/api/ping").permitAll() + .antMatchers("/api/date-format").permitAll() + .antMatchers("/api/healthcheck").permitAll() + .antMatchers("/api/releaseInfo").permitAll() + .antMatchers("/api/account/reset-password/init").permitAll() + .antMatchers("/api/account/reset-password/finish").permitAll() + .antMatchers("/api/images/all").permitAll() + .antMatchers("/api/tfa/verifyCode").hasAuthority(AuthoritiesConstants.PRE_VERIFICATION_USER) + .antMatchers("/api/utm-incident-jobs").hasAuthority(AuthoritiesConstants.ADMIN) + .antMatchers("/api/utm-incident-jobs/**").hasAuthority(AuthoritiesConstants.ADMIN) + .antMatchers("/api/utm-incident-variables/**").hasAuthority(AuthoritiesConstants.ADMIN) + .antMatchers(HttpMethod.GET, "/api/utm-incident-variables").hasAnyAuthority() + .antMatchers("/api/custom-reports/**").denyAll() + .antMatchers("/api/**").hasAnyAuthority(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER) + .antMatchers("/ws/topic").hasAuthority(AuthoritiesConstants.ADMIN) + .antMatchers("/ws/**").permitAll() + .antMatchers("/management/info").permitAll() + .antMatchers("/management/**").hasAnyAuthority(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER) + .and() + .apply(securityConfigurerAdapterForJwt()) + .and() + .apply(securityConfigurerAdapterForInternalApiKey()); } diff --git a/backend/src/main/java/com/park/utmstack/domain/incident_response/UtmIncidentVariable.java b/backend/src/main/java/com/park/utmstack/domain/incident_response/UtmIncidentVariable.java new file mode 100644 index 000000000..e7a4ac5f8 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/domain/incident_response/UtmIncidentVariable.java @@ -0,0 +1,106 @@ +package com.park.utmstack.domain.incident_response; + + +import javax.persistence.*; +import java.io.Serializable; +import java.time.Instant; + +/** + * A UtmIncidentVariable. + */ +@Entity +@Table(name = "utm_incident_variables") +public class UtmIncidentVariable implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "variable_name") + private String variableName; + + @Column(name = "variable_value") + private String variableValue; + + @Column(name = "variable_description") + private String variableDescription; + + @Column(name = "is_secret") + private boolean isSecret; + + @Column(name = "created_by") + private String createdBy; + + @Column(name = "last_modified_date") + private Instant lastModifiedDate; + + @Column(name = "last_modified_by") + private String lastModifiedBy; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getVariableName() { + return variableName; + } + + public void setVariableName(String variableName) { + this.variableName = variableName; + } + + public String getVariableValue() { + return variableValue; + } + + public void setVariableValue(String variableValue) { + this.variableValue = variableValue; + } + + public boolean isSecret() { + return isSecret; + } + + public void setSecret(boolean secret) { + isSecret = secret; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Instant getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(Instant lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public String getVariableDescription() { + return variableDescription; + } + + public void setVariableDescription(String variableDescription) { + this.variableDescription = variableDescription; + } +} diff --git a/backend/src/main/java/com/park/utmstack/repository/incident_response/UtmIncidentVariableRepository.java b/backend/src/main/java/com/park/utmstack/repository/incident_response/UtmIncidentVariableRepository.java new file mode 100644 index 000000000..b53d31281 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/repository/incident_response/UtmIncidentVariableRepository.java @@ -0,0 +1,23 @@ +package com.park.utmstack.repository.incident_response; + +import com.park.utmstack.domain.incident_response.UtmIncidentAction; +import com.park.utmstack.domain.incident_response.UtmIncidentVariable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + + +/** + * Spring Data repository for the UtmIncidentAction entity. + */ +@SuppressWarnings("unused") +@Repository +public interface UtmIncidentVariableRepository extends JpaRepository, JpaSpecificationExecutor { + + Optional findByVariableName(String variable); + + List findAllByVariableNameIn(List variables); +} diff --git a/backend/src/main/java/com/park/utmstack/service/alert_response_rule/UtmAlertResponseRuleService.java b/backend/src/main/java/com/park/utmstack/service/alert_response_rule/UtmAlertResponseRuleService.java index 9038a4068..815e42f3e 100644 --- a/backend/src/main/java/com/park/utmstack/service/alert_response_rule/UtmAlertResponseRuleService.java +++ b/backend/src/main/java/com/park/utmstack/service/alert_response_rule/UtmAlertResponseRuleService.java @@ -14,6 +14,7 @@ import com.park.utmstack.domain.application_events.enums.ApplicationEventType; import com.park.utmstack.domain.chart_builder.types.query.FilterType; import com.park.utmstack.domain.chart_builder.types.query.OperatorType; +import com.park.utmstack.domain.incident_response.UtmIncidentVariable; import com.park.utmstack.domain.shared_types.AlertType; import com.park.utmstack.repository.alert_response_rule.UtmAlertResponseRuleExecutionRepository; import com.park.utmstack.repository.alert_response_rule.UtmAlertResponseRuleHistoryRepository; @@ -25,7 +26,9 @@ import com.park.utmstack.service.dto.agent_manager.AgentDTO; import com.park.utmstack.service.dto.agent_manager.AgentStatusEnum; import com.park.utmstack.service.grpc.CommandResult; +import com.park.utmstack.service.incident_response.UtmIncidentVariableService; import com.park.utmstack.service.incident_response.grpc_impl.IncidentResponseCommandService; +import com.park.utmstack.util.CipherUtil; import com.park.utmstack.util.UtilJson; import com.park.utmstack.util.exceptions.UtmNotImplementedException; import io.grpc.stub.StreamObserver; @@ -59,6 +62,7 @@ public class UtmAlertResponseRuleService { private final AgentService agentService; private final IncidentResponseCommandService incidentResponseCommandService; private final UtmAlertResponseRuleExecutionRepository alertResponseRuleExecutionRepository; + private final UtmIncidentVariableService utmIncidentVariableService; public UtmAlertResponseRuleService(UtmAlertResponseRuleRepository alertResponseRuleRepository, UtmAlertResponseRuleHistoryRepository alertResponseRuleHistoryRepository, @@ -66,7 +70,8 @@ public UtmAlertResponseRuleService(UtmAlertResponseRuleRepository alertResponseR ApplicationEventService eventService, AgentService agentService, IncidentResponseCommandService incidentResponseCommandService, - UtmAlertResponseRuleExecutionRepository alertResponseRuleExecutionRepository) { + UtmAlertResponseRuleExecutionRepository alertResponseRuleExecutionRepository, + UtmIncidentVariableService utmIncidentVariableService) { this.alertResponseRuleRepository = alertResponseRuleRepository; this.alertResponseRuleHistoryRepository = alertResponseRuleHistoryRepository; this.networkScanRepository = networkScanRepository; @@ -74,6 +79,7 @@ public UtmAlertResponseRuleService(UtmAlertResponseRuleRepository alertResponseR this.agentService = agentService; this.incidentResponseCommandService = incidentResponseCommandService; this.alertResponseRuleExecutionRepository = alertResponseRuleExecutionRepository; + this.utmIncidentVariableService = utmIncidentVariableService; } public UtmAlertResponseRule save(UtmAlertResponseRule alertResponseRule) { @@ -81,7 +87,7 @@ public UtmAlertResponseRule save(UtmAlertResponseRule alertResponseRule) { try { if (alertResponseRule.getId() != null) { UtmAlertResponseRule current = alertResponseRuleRepository.findById(alertResponseRule.getId()) - .orElseThrow(() -> new RuntimeException(String.format("Incident response rule with ID: %1$s not found", alertResponseRule.getId()))); + .orElseThrow(() -> new RuntimeException(String.format("Incident response rule with ID: %1$s not found", alertResponseRule.getId()))); alertResponseRuleHistoryRepository.save(new UtmAlertResponseRuleHistory(new UtmAlertResponseRuleDTO(current))); } return alertResponseRuleRepository.save(alertResponseRule); @@ -113,8 +119,8 @@ public Map> resolveFilterValues() { final String ctx = CLASSNAME + ".resolveFilterValues"; try { return Map.of( - "agentPlatform", alertResponseRuleRepository.findAgentPlatformValues(), - "users", alertResponseRuleRepository.findUserValues() + "agentPlatform", alertResponseRuleRepository.findAgentPlatformValues(), + "users", alertResponseRuleRepository.findUserValues() ); } catch (Exception e) { throw new RuntimeException(ctx + ": " + e.getLocalizedMessage()); @@ -132,7 +138,11 @@ public void evaluateRules(List alerts) { // Excluding alerts tagged as false positive alerts = alerts.stream().filter(a -> (CollectionUtils.isEmpty(a.getTags()) || !a.getTags().contains("False positive"))) - .collect(Collectors.toList()); + .collect(Collectors.toList()); + + // Do nothing if there is no valid alerts to check + if (CollectionUtils.isEmpty(alerts)) + return; // Do nothing if there is no valid alerts to check if (CollectionUtils.isEmpty(alerts)) @@ -144,7 +154,6 @@ public void evaluateRules(List alerts) { if (CollectionUtils.isEmpty(agentNames)) continue; - // Matching agents (these are the alerts made from logs coming from an agent) //------------------------------------------------------------------------------------------ createResponseRuleExecution(rule,alertJsonArray,agentNames,true); @@ -199,6 +208,7 @@ private void createResponseRuleExecution (UtmAlertResponseRule rule, String aler exe.setAlertId(UtilJson.read("$.id", matchAsJson)); exe.setRuleId(rule.getId()); + exe.setCommand(buildCommand(rule.getRuleCmd(), matchAsJson)); exe.setExecutionStatus(RuleExecutionStatus.PENDING); alertResponseRuleExecutionRepository.save(exe); @@ -283,26 +293,26 @@ public void executeRuleCommands() { if (agent.getStatus().equals(AgentStatusEnum.ONLINE)) { String reason = "The incident response automation executed this command because it was accomplished the conditions of the rule with ID: " + cmd.getRuleId(); final StringBuilder results = new StringBuilder(); - incidentResponseCommandService.sendCommand(agent.getAgentKey(), cmd.getCommand(), "INCIDENT_RESPONSE_AUTOMATION", - cmd.getRuleId().toString(), reason, Constants.SYSTEM_ACCOUNT, new StreamObserver<>() { - @Override - public void onNext(CommandResult commandResult) { - results.append(commandResult.getResult()); - } - - @Override - public void onError(Throwable throwable) { - - } - - @Override - public void onCompleted() { - cmd.setCommandResult(results.toString()); - cmd.setExecutionStatus(RuleExecutionStatus.EXECUTED); - cmd.setNonExecutionCause(null); - alertResponseRuleExecutionRepository.save(cmd); - } - }); + incidentResponseCommandService.sendCommand(agent.getAgentKey(), utmIncidentVariableService.replaceVariablesInCommand(cmd.getCommand()), "INCIDENT_RESPONSE_AUTOMATION", + cmd.getRuleId().toString(), reason, Constants.SYSTEM_ACCOUNT, new StreamObserver<>() { + @Override + public void onNext(CommandResult commandResult) { + results.append(commandResult.getResult()); + } + + @Override + public void onError(Throwable throwable) { + + } + + @Override + public void onCompleted() { + cmd.setCommandResult(results.toString()); + cmd.setExecutionStatus(RuleExecutionStatus.EXECUTED); + cmd.setNonExecutionCause(null); + alertResponseRuleExecutionRepository.save(cmd); + } + }); } else { if (cmd.getExecutionRetries() < Constants.IRA_EXECUTION_RETRIES) { cmd.setExecutionStatus(RuleExecutionStatus.PENDING); diff --git a/backend/src/main/java/com/park/utmstack/service/dto/incident_response/UtmIncidentVariableCriteria.java b/backend/src/main/java/com/park/utmstack/service/dto/incident_response/UtmIncidentVariableCriteria.java new file mode 100644 index 000000000..c4a851591 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/dto/incident_response/UtmIncidentVariableCriteria.java @@ -0,0 +1,36 @@ +package com.park.utmstack.service.dto.incident_response; + +import tech.jhipster.service.filter.*; + +import java.io.Serializable; + +/** + * Criteria class for the UtmIncidentAction entity. This class is used in UtmIncidentActionResource to receive all the + * possible filtering options from the Http GET request parameters. For example the following could be a valid requests: + * /utm-incident-actions?id.greaterThan=5&attr1.contains=something&attr2.specified=false + * As Spring is unable to properly convert the types, unless specific {@link Filter} class are used, we need to use fix type + * specific filters. + */ +public class UtmIncidentVariableCriteria implements Serializable { + + private static final long serialVersionUID = 1L; + + private LongFilter id; + private StringFilter variableName; + + public LongFilter getId() { + return id; + } + + public void setId(LongFilter id) { + this.id = id; + } + + public StringFilter getVariableName() { + return variableName; + } + + public void setVariableName(StringFilter variableName) { + this.variableName = variableName; + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/incident_response/UtmIncidentVariableQueryService.java b/backend/src/main/java/com/park/utmstack/service/incident_response/UtmIncidentVariableQueryService.java new file mode 100644 index 000000000..0edf5a3b7 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/incident_response/UtmIncidentVariableQueryService.java @@ -0,0 +1,92 @@ +package com.park.utmstack.service.incident_response; + +import com.park.utmstack.domain.incident_response.UtmIncidentVariable; +import com.park.utmstack.domain.incident_response.UtmIncidentVariable_; +import com.park.utmstack.repository.incident_response.UtmIncidentVariableRepository; +import com.park.utmstack.service.dto.incident_response.UtmIncidentVariableCriteria; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import tech.jhipster.service.QueryService; + +import java.util.List; + +/** + * Service for executing complex queries for UtmIncidentVariable entities in the database. The main input is a {@link + * UtmIncidentVariableCriteria} which gets converted to {@link Specification}, in a way that all the filters must apply. It + * returns a {@link List} of {@link UtmIncidentVariable} or a {@link Page} of {@link UtmIncidentVariable} which fulfills the + * criteria. + */ +@Service +@Transactional(readOnly = true) +public class UtmIncidentVariableQueryService extends QueryService { + + private final Logger log = LoggerFactory.getLogger(UtmIncidentVariableQueryService.class); + + private final UtmIncidentVariableRepository utmIncidentActionRepository; + + public UtmIncidentVariableQueryService(UtmIncidentVariableRepository utmIncidentActionRepository) { + this.utmIncidentActionRepository = utmIncidentActionRepository; + } + + /** + * Return a {@link List} of {@link UtmIncidentVariable} which matches the criteria from the database + * + * @param criteria The object which holds all the filters, which the entities should match. + * @return the matching entities. + */ + @Transactional(readOnly = true) + public List findByCriteria(UtmIncidentVariableCriteria criteria) { + log.debug("find by criteria : {}", criteria); + final Specification specification = createSpecification(criteria); + return utmIncidentActionRepository.findAll(specification); + } + + /** + * Return a {@link Page} of {@link UtmIncidentVariable} which matches the criteria from the database + * + * @param criteria The object which holds all the filters, which the entities should match. + * @param page The page, which should be returned. + * @return the matching entities. + */ + @Transactional(readOnly = true) + public Page findByCriteria(UtmIncidentVariableCriteria criteria, Pageable page) { + log.debug("find by criteria : {}, page: {}", criteria, page); + final Specification specification = createSpecification(criteria); + return utmIncidentActionRepository.findAll(specification, page); + } + + /** + * Return the number of matching entities in the database + * + * @param criteria The object which holds all the filters, which the entities should match. + * @return the number of matching entities. + */ + @Transactional(readOnly = true) + public long countByCriteria(UtmIncidentVariableCriteria criteria) { + log.debug("count by criteria : {}", criteria); + final Specification specification = createSpecification(criteria); + return utmIncidentActionRepository.count(specification); + } + + /** + * Function to convert UtmIncidentVariableCriteria to a {@link Specification} + */ + private Specification createSpecification(UtmIncidentVariableCriteria criteria) { + Specification specification = Specification.where(null); + if (criteria != null) { + if (criteria.getId() != null) { + specification = specification.and(buildSpecification(criteria.getId(), UtmIncidentVariable_.id)); + } + if (criteria.getVariableName() != null) { + specification = specification.and( + buildStringSpecification(criteria.getVariableName(), UtmIncidentVariable_.variableName)); + } + } + return specification; + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/incident_response/UtmIncidentVariableService.java b/backend/src/main/java/com/park/utmstack/service/incident_response/UtmIncidentVariableService.java new file mode 100644 index 000000000..f8c22f38f --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/incident_response/UtmIncidentVariableService.java @@ -0,0 +1,132 @@ +package com.park.utmstack.service.incident_response; + +import com.park.utmstack.config.Constants; +import com.park.utmstack.domain.incident_response.UtmIncidentVariable; +import com.park.utmstack.repository.incident_response.UtmIncidentVariableRepository; +import com.park.utmstack.util.CipherUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Service Implementation for managing UtmIncidentVariable. + */ +@Service +@Transactional +public class UtmIncidentVariableService { + + private final Logger log = LoggerFactory.getLogger(UtmIncidentVariableService.class); + + private final UtmIncidentVariableRepository utmIncidentVariableRepository; + + public UtmIncidentVariableService(UtmIncidentVariableRepository utmIncidentVariableRepository) { + + this.utmIncidentVariableRepository = utmIncidentVariableRepository; + } + + /** + * Save a utmIncidentVariable. + * + * @param utmIncidentVariable the entity to save + * @return the persisted entity + */ + public UtmIncidentVariable save(UtmIncidentVariable utmIncidentVariable) { + log.debug("Request to save UtmIncidentVariable : {}", utmIncidentVariable); + if (utmIncidentVariable.isSecret()) { + String currentValue = utmIncidentVariable.getVariableValue(); + utmIncidentVariable.setVariableValue(CipherUtil.encrypt(currentValue, System.getenv(Constants.ENV_ENCRYPTION_KEY))); + } + return utmIncidentVariableRepository.save(utmIncidentVariable); + } + + /** + * Get all the utmIncidentVariables. + * + * @param pageable the pagination information + * @return the list of entities + */ + @Transactional(readOnly = true) + public Page findAll(Pageable pageable) { + log.debug("Request to get all UtmIncidentVariables"); + return utmIncidentVariableRepository.findAll(pageable); + } + + + /** + * Get one utmIncidentVariable by id. + * + * @param id the id of the entity + * @return the entity + */ + @Transactional(readOnly = true) + public Optional findOne(Long id) { + log.debug("Request to get UtmIncidentVariable : {}", id); + return utmIncidentVariableRepository.findById(id); + } + + /** + * Get one utmIncidentVariable by name. + * + * @param variableName the name of the variable + * @return the entity + */ + @Transactional(readOnly = true) + public Optional findByName(String variableName) { + return utmIncidentVariableRepository.findByVariableName(variableName); + } + + /** + * Get all utmIncidentVariable by name in list. + * + * @param variableNames the names of the variables to list + * @return the entity + */ + @Transactional(readOnly = true) + public List findByVariablesByNames(List variableNames) { + return utmIncidentVariableRepository.findAllByVariableNameIn(variableNames); + } + + + /** + * Delete the utmIncidentVariable by id. + * + * @param id the id of the entity + */ + public void delete(Long id) { + log.debug("Request to delete UtmIncidentVariable : {}", id); + utmIncidentVariableRepository.deleteById(id); + } + + public String replaceVariablesInCommand(String command) { + List variableNames = new ArrayList<>(); + Pattern pattern = Pattern.compile("\\$\\[variables\\.(.*?)]"); + Matcher matcher = pattern.matcher(command); + + while (matcher.find()) { + variableNames.add(matcher.group(1)); + } + + List variables = findByVariablesByNames(variableNames); + if (CollectionUtils.isEmpty(variables)) + return command; + + for (UtmIncidentVariable var : variables) { + String currentValue = var.getVariableValue(); + if (var.isSecret()) + currentValue = "$[" + var.getVariableName() + ":" + currentValue + "]"; + command = command.replace("$[variables." + var.getVariableName() + "]", currentValue); + } + + return command; + } +} diff --git a/backend/src/main/java/com/park/utmstack/web/rest/incident_response/UTMIncidentCommandWebsocket.java b/backend/src/main/java/com/park/utmstack/web/rest/incident_response/UTMIncidentCommandWebsocket.java index eaa07921a..7b1877feb 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/incident_response/UTMIncidentCommandWebsocket.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/incident_response/UTMIncidentCommandWebsocket.java @@ -7,6 +7,7 @@ import com.park.utmstack.service.dto.agent_manager.AgentStatusEnum; import com.park.utmstack.service.grpc.CommandResult; import com.park.utmstack.service.grpc.Hostname; +import com.park.utmstack.service.incident_response.UtmIncidentVariableService; import com.park.utmstack.service.incident_response.grpc_impl.IncidentResponseCommandService; import com.park.utmstack.web.rest.errors.AgentNotfoundException; import com.park.utmstack.web.rest.errors.AgentOfflineException; @@ -32,12 +33,15 @@ public class UTMIncidentCommandWebsocket { private final AgentGrpcService agentGrpcService; + private final UtmIncidentVariableService utmIncidentVariableService; + public UTMIncidentCommandWebsocket(SimpMessagingTemplate messagingTemplate, IncidentResponseCommandService incidentResponseCommandService, - AgentGrpcService agentGrpcService) { + AgentGrpcService agentGrpcService, UtmIncidentVariableService utmIncidentVariableService) { this.messagingTemplate = messagingTemplate; this.incidentResponseCommandService = incidentResponseCommandService; this.agentGrpcService = agentGrpcService; + this.utmIncidentVariableService = utmIncidentVariableService; } @MessageMapping("/command/{hostname}") @@ -45,11 +49,11 @@ public void processCommand(@NotNull String command, @DestinationVariable @NotNul final String ctx = CLASSNAME + ".processCommand"; try { String executedBy = SecurityUtils.getCurrentUserLogin() - .orElseThrow(() -> new InternalServerErrorException("Current user login not found")); + .orElseThrow(() -> new InternalServerErrorException("Current user login not found")); String destination = String.format("/topic/%1$s", hostname); Hostname req = Hostname.newBuilder() - .setHostname(hostname) - .build(); + .setHostname(hostname) + .build(); try { AgentDTO agentDTO = agentGrpcService.getAgentByHostname(req); if (agentDTO.getStatus() == AgentStatusEnum.OFFLINE) { @@ -57,24 +61,24 @@ public void processCommand(@NotNull String command, @DestinationVariable @NotNul } CommandVM commandVM = new Gson().fromJson(command, CommandVM.class); - incidentResponseCommandService.sendCommand(agentDTO.getAgentKey(), commandVM.getCommand(), commandVM.getOriginType(), - commandVM.getOriginId(), commandVM.getReason(), executedBy, new StreamObserver<>() { - @Override - public void onNext(CommandResult value) { - messagingTemplate.convertAndSendToUser(executedBy, destination, value.getResult()); - } + incidentResponseCommandService.sendCommand(agentDTO.getAgentKey(), utmIncidentVariableService.replaceVariablesInCommand(commandVM.getCommand()), commandVM.getOriginType(), + commandVM.getOriginId(), commandVM.getReason(), executedBy, new StreamObserver<>() { + @Override + public void onNext(CommandResult value) { + messagingTemplate.convertAndSendToUser(executedBy, destination, value.getResult()); + } - @Override - public void onError(Throwable t) { - String msg = ctx + ": " + t.getLocalizedMessage(); - log.error(msg); - messagingTemplate.convertAndSendToUser(executedBy, destination, t.getLocalizedMessage()); - } + @Override + public void onError(Throwable t) { + String msg = ctx + ": " + t.getLocalizedMessage(); + log.error(msg); + messagingTemplate.convertAndSendToUser(executedBy, destination, t.getLocalizedMessage()); + } - @Override - public void onCompleted() { - } - }); + @Override + public void onCompleted() { + } + }); } catch (Exception exception) { throw new AgentNotfoundException(); } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/incident_response/UtmIncidentVariableResource.java b/backend/src/main/java/com/park/utmstack/web/rest/incident_response/UtmIncidentVariableResource.java new file mode 100644 index 000000000..3424e45f9 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/web/rest/incident_response/UtmIncidentVariableResource.java @@ -0,0 +1,153 @@ +package com.park.utmstack.web.rest.incident_response; + +import com.park.utmstack.domain.application_events.enums.ApplicationEventType; +import com.park.utmstack.domain.incident_response.UtmIncidentVariable; +import com.park.utmstack.security.SecurityUtils; +import com.park.utmstack.service.application_events.ApplicationEventService; +import com.park.utmstack.service.dto.incident_response.UtmIncidentVariableCriteria; +import com.park.utmstack.service.incident_response.UtmIncidentVariableQueryService; +import com.park.utmstack.service.incident_response.UtmIncidentVariableService; +import com.park.utmstack.web.rest.util.HeaderUtil; +import com.park.utmstack.web.rest.util.PaginationUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.Instant; +import java.util.List; + +/** + * REST controller for managing UtmIncidentVariable. + */ +@RestController +@RequestMapping("/api") +public class UtmIncidentVariableResource { + + private final Logger log = LoggerFactory.getLogger(UtmIncidentVariableResource.class); + + private static final String ENTITY_NAME = "utmIncidentVariable"; + private static final String CLASS_NAME = "UtmIncidentVariableResource"; + + private final UtmIncidentVariableService utmIncidentVariableService; + private final UtmIncidentVariableQueryService utmIncidentVariableQueryService; + + private final ApplicationEventService applicationEventService; + + public UtmIncidentVariableResource(UtmIncidentVariableService utmIncidentVariableService, + UtmIncidentVariableQueryService utmIncidentVariableQueryService, + ApplicationEventService applicationEventService) { + this.utmIncidentVariableService = utmIncidentVariableService; + this.utmIncidentVariableQueryService = utmIncidentVariableQueryService; + this.applicationEventService = applicationEventService; + } + + /** + * POST /utm-incident-variables : Create a new Incident variable. + * + * @param utmIncidentVariable the utmIncidentVariable to create + * @return the ResponseEntity with status 201 (Created) and with body the new utmIncidentVariable, or with status 400 (Bad + * Request) if the utmIncidentVariable has already an ID + */ + @PostMapping("/utm-incident-variables") + public ResponseEntity createUtmIncidentVariable(@RequestBody UtmIncidentVariable utmIncidentVariable) { + final String ctx = CLASS_NAME + ".createUtmIncidentVariable"; + try { + if (utmIncidentVariable.getId() != null) + throw new Exception("A new utmIncidentVariable cannot already have an ID"); + + utmIncidentVariable.setLastModifiedDate(Instant.now()); + utmIncidentVariable.setCreatedBy(SecurityUtils.getCurrentUserLogin().orElse("system")); + + UtmIncidentVariable result = utmIncidentVariableService.save(utmIncidentVariable); + return ResponseEntity.ok(result); + } catch (Exception e) { + String msg = ctx + ": " + e.getMessage(); + log.error(msg); + applicationEventService.createEvent(msg, ApplicationEventType.ERROR); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( + HeaderUtil.createFailureAlert("", "", msg)).body(null); + } + } + + /** + * PUT /utm-incident-variables : Updates an existing variable. + * + * @param utmIncidentVariable the utmIncidentVariable to update + * @return the ResponseEntity with status 200 (OK) and with body the updated utmIncidentVariable, or with status 400 (Bad + * Request) if the utmIncidentVariable is not valid, or with status 500 (Internal Server Error) if the utmIncidentVariable + * couldn't be updated + */ + @PutMapping("/utm-incident-variables") + public ResponseEntity updateUtmIncidentVariable(@RequestBody UtmIncidentVariable utmIncidentVariable) { + final String ctx = CLASS_NAME + ".updateUtmIncidentVariable"; + + try { + if (utmIncidentVariable.getId() == null) + throw new Exception("Id can't be null"); + + utmIncidentVariable.setLastModifiedDate(Instant.now()); + utmIncidentVariable.setLastModifiedBy(SecurityUtils.getCurrentUserLogin().orElse("system")); + + UtmIncidentVariable result = utmIncidentVariableService.save(utmIncidentVariable); + return ResponseEntity.ok(result); + } catch (Exception e) { + String msg = ctx + ": " + e.getMessage(); + log.error(msg); + applicationEventService.createEvent(msg, ApplicationEventType.ERROR); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( + HeaderUtil.createFailureAlert("", "", msg)).body(null); + } + } + + /** + * GET /utm-incident-variables : get all the variables. + * + * @param pageable the pagination information + * @param criteria the criterias which the requested entities should match + * @return the ResponseEntity with status 200 (OK) and the list of utmIncidentVariables in body + */ + @GetMapping("/utm-incident-variables") + public ResponseEntity> getAllUtmIncidentVariables(UtmIncidentVariableCriteria criteria, + Pageable pageable) { + final String ctx = CLASS_NAME + ".getAllUtmIncidentVariables"; + try { + Page page = utmIncidentVariableQueryService.findByCriteria(criteria, pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/utm-incident-actions"); + List content = page.getContent(); + + return ResponseEntity.ok().headers(headers).body(content); + } catch (Exception e) { + String msg = ctx + ": " + e.getMessage(); + log.error(msg); + applicationEventService.createEvent(msg, ApplicationEventType.ERROR); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( + HeaderUtil.createFailureAlert("", "", msg)).body(null); + } + } + + /** + * DELETE /utm-incident-variables/:id : delete the "id" utmIncidentVariable. + * + * @param id the id of the utmIncidentVariable to delete + * @return the ResponseEntity with status 200 (OK) + */ + @DeleteMapping("/utm-incident-variables/{id}") + public ResponseEntity deleteUtmIncidentVariable(@PathVariable Long id) { + final String ctx = CLASS_NAME + ".deleteUtmIncidentVariable"; + try { + utmIncidentVariableService.delete(id); + return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert(ENTITY_NAME, id.toString())).build(); + } catch (Exception e) { + String msg = ctx + ": " + e.getMessage(); + log.error(msg); + applicationEventService.createEvent(msg, ApplicationEventType.ERROR); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers( + HeaderUtil.createFailureAlert("", "", msg)).body(null); + } + } +} diff --git a/backend/src/main/resources/config/liquibase/changelog/20240205001_add_entity_IncidentResponseVariables.xml b/backend/src/main/resources/config/liquibase/changelog/20240205001_add_entity_IncidentResponseVariables.xml new file mode 100644 index 000000000..4fadd59cc --- /dev/null +++ b/backend/src/main/resources/config/liquibase/changelog/20240205001_add_entity_IncidentResponseVariables.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/main/resources/config/liquibase/master.xml b/backend/src/main/resources/config/liquibase/master.xml index 947f94cb3..1c4d83ae1 100644 --- a/backend/src/main/resources/config/liquibase/master.xml +++ b/backend/src/main/resources/config/liquibase/master.xml @@ -38,5 +38,8 @@ + + + diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 64065d939..1c0333690 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -3,152 +3,162 @@ import {RouterModule, Routes} from '@angular/router'; import {UserRouteAccessService} from './core/auth/user-route-access-service'; import {ConfirmIdentityComponent} from './shared/components/auth/confirm-identity/confirm-identity.component'; import {LoginComponent} from './shared/components/auth/login/login.component'; -import {PasswordResetFinishComponent} from './shared/components/auth/password-reset/finish/password-reset-finish.component'; +import { + PasswordResetFinishComponent +} from './shared/components/auth/password-reset/finish/password-reset-finish.component'; import {TotpComponent} from './shared/components/auth/totp/totp.component'; -import {WelcomeToUtmstackComponent} from './shared/components/getting-started/welcome-to-utmstack/welcome-to-utmstack.component'; +import { + WelcomeToUtmstackComponent +} from './shared/components/getting-started/welcome-to-utmstack/welcome-to-utmstack.component'; import {NotFoundComponent} from './shared/components/not-found/not-found.component'; import {UtmLiteVersionComponent} from './shared/components/utm-lite-version/utm-lite-version.component'; import {UtmModuleDisabledComponent} from './shared/components/utm-module-disabled/utm-module-disabled.component'; import {ADMIN_ROLE, USER_ROLE} from './shared/constants/global.constant'; const routes: Routes = [ - {path: '', redirectTo: 'login', pathMatch: 'full'}, - { - path: 'dashboard', - loadChildren: './dashboard/dashboard.module#UtmDashboardModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, - { - path: 'data', - loadChildren: './data-management/data-management.module#DataManagementModule', - canActivate: [UserRouteAccessService], - data: {authorities: [ADMIN_ROLE, USER_ROLE]} - }, - { - path: 'explore', - loadChildren: './filebrowser/filebrowser.module#FileBrowserModule', - canActivate: [UserRouteAccessService], - data: {authorities: [ADMIN_ROLE, USER_ROLE]} - }, - { - path: 'profile', - loadChildren: './account/account.module#UtmAccountModule', - canActivate: [UserRouteAccessService], - data: {authorities: [ADMIN_ROLE, USER_ROLE]} - }, - // { - // path: 'vulnerability-scanner', - // loadChildren: './vulnerability-scanner/vulnerability-scanner.module#VulnerabilityScannerModule', - // canActivate: [UserRouteAccessService, LiteModeRouteAccessService, ActiveModuleRouteAccessService], - // data: {authorities: [USER_ROLE, ADMIN_ROLE], module: UtmModulesEnum.VULNERABILITIES} - // }, - { - path: 'data-sources', - loadChildren: './assets-discover/assets-discover.module#AssetsDiscoverModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, - { - path: 'management', - loadChildren: './admin/admin.module#AdminModule', - canActivate: [UserRouteAccessService], - data: {authorities: [ADMIN_ROLE]} - }, - { - path: 'creator', - loadChildren: './graphic-builder/graphic-builder.module#GraphicBuilderModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, - { - path: 'app-management', - loadChildren: './app-management/app-management.module#AppManagementModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, - { - path: 'integrations', - loadChildren: './app-module/app-module.module#AppModuleModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, - { - path: 'active-directory', - loadChildren: './active-directory/active-directory.module#ActiveDirectoryModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, - { - path: 'discover', - loadChildren: './log-analyzer/log-analyzer.module#LogAnalyzerModule', - canActivate: [UserRouteAccessService], - data: {authorities: [ADMIN_ROLE, USER_ROLE]} - }, - { - path: 'compliance', - loadChildren: './compliance/compliance.module#ComplianceModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, - { - path: 'data-parsing', - loadChildren: './logstash/logstash.module#LogstashModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, + {path: '', redirectTo: 'login', pathMatch: 'full'}, + { + path: 'dashboard', + loadChildren: './dashboard/dashboard.module#UtmDashboardModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + { + path: 'data', + loadChildren: './data-management/data-management.module#DataManagementModule', + canActivate: [UserRouteAccessService], + data: {authorities: [ADMIN_ROLE, USER_ROLE]} + }, + { + path: 'explore', + loadChildren: './filebrowser/filebrowser.module#FileBrowserModule', + canActivate: [UserRouteAccessService], + data: {authorities: [ADMIN_ROLE, USER_ROLE]} + }, + { + path: 'profile', + loadChildren: './account/account.module#UtmAccountModule', + canActivate: [UserRouteAccessService], + data: {authorities: [ADMIN_ROLE, USER_ROLE]} + }, + // { + // path: 'vulnerability-scanner', + // loadChildren: './vulnerability-scanner/vulnerability-scanner.module#VulnerabilityScannerModule', + // canActivate: [UserRouteAccessService, LiteModeRouteAccessService, ActiveModuleRouteAccessService], + // data: {authorities: [USER_ROLE, ADMIN_ROLE], module: UtmModulesEnum.VULNERABILITIES} + // }, + { + path: 'data-sources', + loadChildren: './assets-discover/assets-discover.module#AssetsDiscoverModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + { + path: 'management', + loadChildren: './admin/admin.module#AdminModule', + canActivate: [UserRouteAccessService], + data: {authorities: [ADMIN_ROLE]} + }, + { + path: 'creator', + loadChildren: './graphic-builder/graphic-builder.module#GraphicBuilderModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + { + path: 'app-management', + loadChildren: './app-management/app-management.module#AppManagementModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + { + path: 'integrations', + loadChildren: './app-module/app-module.module#AppModuleModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + { + path: 'variables', + loadChildren: './automation-variables/automation-variables.module#AutomationVariablesModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + { + path: 'active-directory', + loadChildren: './active-directory/active-directory.module#ActiveDirectoryModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + { + path: 'discover', + loadChildren: './log-analyzer/log-analyzer.module#LogAnalyzerModule', + canActivate: [UserRouteAccessService], + data: {authorities: [ADMIN_ROLE, USER_ROLE]} + }, + { + path: 'compliance', + loadChildren: './compliance/compliance.module#ComplianceModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + { + path: 'data-parsing', + loadChildren: './logstash/logstash.module#LogstashModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, - { - path: 'incident-response', - loadChildren: './incident-response/incident-response.module#IncidentResponseModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, + { + path: 'incident-response', + loadChildren: './incident-response/incident-response.module#IncidentResponseModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, - { - path: 'incident', - loadChildren: './incident/incident.module#IncidentModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, - // { - // path: 'reports', - // loadChildren: './report/report.module#ReportModule', - // canActivate: [UserRouteAccessService], - // data: {authorities: [USER_ROLE, ADMIN_ROLE]} - // }, - { - path: 'iframe', - loadChildren: './data-management/alert-management/alert-management.module#AlertManagementModule', - canActivate: [UserRouteAccessService], - data: {authorities: [USER_ROLE, ADMIN_ROLE]} - }, - { - path: 'getting-started', - component: WelcomeToUtmstackComponent, - canActivate: [UserRouteAccessService], - data: {authorities: [ADMIN_ROLE, USER_ROLE]} - }, - {path: '', component: LoginComponent}, - {path: 'totp', component: TotpComponent}, - {path: 'reset/finish', component: PasswordResetFinishComponent}, - {path: 'page-not-found', component: NotFoundComponent}, - {path: 'confirm-identity/:id', component: ConfirmIdentityComponent}, - {path: 'module-disabled', component: UtmModuleDisabledComponent}, - {path: 'lite-mode', component: UtmLiteVersionComponent}, - {path: '**', redirectTo: 'page-not-found'}, + { + path: 'incident', + loadChildren: './incident/incident.module#IncidentModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + // { + // path: 'reports', + // loadChildren: './report/report.module#ReportModule', + // canActivate: [UserRouteAccessService], + // data: {authorities: [USER_ROLE, ADMIN_ROLE]} + // }, + { + path: 'iframe', + loadChildren: './data-management/alert-management/alert-management.module#AlertManagementModule', + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + { + path: 'getting-started', + component: WelcomeToUtmstackComponent, + canActivate: [UserRouteAccessService], + data: {authorities: [ADMIN_ROLE, USER_ROLE]} + }, + {path: '', component: LoginComponent}, + {path: 'totp', component: TotpComponent}, + {path: 'reset/finish', component: PasswordResetFinishComponent}, + {path: 'page-not-found', component: NotFoundComponent}, + {path: 'confirm-identity/:id', component: ConfirmIdentityComponent}, + {path: 'module-disabled', component: UtmModuleDisabledComponent}, + {path: 'lite-mode', component: UtmLiteVersionComponent}, + {path: '**', redirectTo: 'page-not-found'}, ]; @NgModule({ - imports: [RouterModule.forRoot(routes, { - useHash: false, - scrollPositionRestoration: 'top', - onSameUrlNavigation: 'reload', - anchorScrolling: 'enabled', - scrollOffset: [0, 64], - })], - exports: [RouterModule] + imports: [RouterModule.forRoot(routes, { + useHash: false, + scrollPositionRestoration: 'top', + onSameUrlNavigation: 'reload', + anchorScrolling: 'enabled', + scrollOffset: [0, 64], + })], + exports: [RouterModule] }) export class AppRoutingModule { } diff --git a/frontend/src/app/automation-variables/automation-variables-routing.module.ts b/frontend/src/app/automation-variables/automation-variables-routing.module.ts new file mode 100644 index 000000000..53c35f3ca --- /dev/null +++ b/frontend/src/app/automation-variables/automation-variables-routing.module.ts @@ -0,0 +1,25 @@ +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {ActiveModuleRouteAccessService} from '../core/auth/active-module-route-access-service'; +import {UserRouteAccessService} from '../core/auth/user-route-access-service'; +import {ADMIN_ROLE, USER_ROLE} from '../shared/constants/global.constant'; +import {VariablesComponent} from "./variables/variables.component"; + +const routes: Routes = [ + {path: '', redirectTo: 'overview'}, + { + path: 'list', + component: VariablesComponent, + canActivate: [UserRouteAccessService], + data: {authorities: [USER_ROLE, ADMIN_ROLE]} + }, + +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class AutomationVariablesRoutingModule { +} + diff --git a/frontend/src/app/automation-variables/automation-variables.module.ts b/frontend/src/app/automation-variables/automation-variables.module.ts new file mode 100644 index 000000000..29c3c5dfa --- /dev/null +++ b/frontend/src/app/automation-variables/automation-variables.module.ts @@ -0,0 +1,18 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {VariablesComponent} from "./variables/variables.component"; +import {UtmSharedModule} from "../shared/utm-shared.module"; +import {NgbModule} from "@ng-bootstrap/ng-bootstrap"; +import {AutomationVariablesRoutingModule} from "./automation-variables-routing.module"; + +@NgModule({ + declarations: [VariablesComponent], + imports: [ + CommonModule, + UtmSharedModule, + NgbModule, + AutomationVariablesRoutingModule + ] +}) +export class AutomationVariablesModule { +} diff --git a/frontend/src/app/automation-variables/variables/variables.component.html b/frontend/src/app/automation-variables/variables/variables.component.html new file mode 100644 index 000000000..9f280ebcc --- /dev/null +++ b/frontend/src/app/automation-variables/variables/variables.component.html @@ -0,0 +1,114 @@ +
+
+
Automation Variables
+
+ +
+
+
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Variable name + ReferenceDescription + Value + + Created by + + Actions +
+ {{ variable.variableName }} + + {{ getVariablePlaceHolder(variable) }} + +

{{ variable.variableDescription }}

+
+ {{ variable.secret ? '*******************' : variable.variableValue }} + + {{ variable.createdBy }} + +
+ + + +
+
+ +
+
+ + +
+
+
+
+ +
+
+
+
+
diff --git a/frontend/src/app/automation-variables/variables/variables.component.scss b/frontend/src/app/automation-variables/variables/variables.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/automation-variables/variables/variables.component.ts b/frontend/src/app/automation-variables/variables/variables.component.ts new file mode 100644 index 000000000..82f225abd --- /dev/null +++ b/frontend/src/app/automation-variables/variables/variables.component.ts @@ -0,0 +1,121 @@ +import {HttpResponse} from '@angular/common/http'; +import {Component, OnInit} from '@angular/core'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {UtmToastService} from '../../shared/alert/utm-toast.service'; +import { + ModalConfirmationComponent +} from '../../shared/components/utm/util/modal-confirmation/modal-confirmation.component'; +import {ITEMS_PER_PAGE} from '../../shared/constants/pagination.constants'; +import {SortEvent} from '../../shared/directives/sortable/type/sort-event'; +import {IrVariableCreateComponent} from '../../shared/components/utm/incident-variables/ir-variable-create/ir-variable-create.component'; +import {IncidentResponseVariableService} from '../../shared/services/incidents/incident-response-variable.service'; +import {IncidentVariableType} from '../../shared/types/incident/incident-variable.type'; + +@Component({ + selector: 'app-variables', + templateUrl: './variables.component.html', + styleUrls: ['./variables.component.scss'] +}) +export class VariablesComponent implements OnInit { + loading = true; + variables: IncidentVariableType[]; + itemsPerPage = 15; + totalItems: number; + request = { + page: 0, + size: ITEMS_PER_PAGE, + sort: '', + 'variableName.contains': null, + }; + + constructor( + private modalService: NgbModal, + private utmToastService: UtmToastService, + private incidentResponseVariableService: IncidentResponseVariableService + ) { + } + + ngOnInit() { + this.getVariables(); + } + + deactivateAction(variable: IncidentVariableType) { + const deleteModalRef = this.modalService.open(ModalConfirmationComponent, {backdrop: 'static', centered: true}); + deleteModalRef.componentInstance.header = 'Delete automation variable'; + deleteModalRef.componentInstance.message = 'Are you sure that you want to delete the variable: \n' + variable.variableName + '?'; + deleteModalRef.componentInstance.confirmBtnText = 'Delete'; + deleteModalRef.componentInstance.confirmBtnIcon = 'icon-cancel-circle2'; + deleteModalRef.componentInstance.confirmBtnType = 'delete'; + deleteModalRef.componentInstance.textDisplay = 'Incident response automation could encounter failures' + + ' when attempting to reference this specific variable.'; + deleteModalRef.componentInstance.textType = 'warning'; + deleteModalRef.result.then(() => { + this.delete(variable); + }); + } + + loadPage(page: number) { + this.request.page = page - 1; + this.getVariables(); + } + + onItemsPerPageChange($event: number) { + this.request.size = $event; + this.getVariables(); + } + + getVariables() { + this.incidentResponseVariableService.query(this.request).subscribe( + (res: HttpResponse) => this.onSuccess(res.body, res.headers), + (res: HttpResponse) => this.onError(res.body) + ); + } + + private onSuccess(data, headers) { + this.totalItems = headers.get('X-Total-Count'); + this.variables = data; + this.loading = false; + } + + private onError(error) { + // this.alertService.error(error.error, error.message, null); + } + + onSort($event: SortEvent) { + this.request.sort = $event.column + ',' + $event.direction; + this.getVariables(); + } + + searchVariable($event: string | number) { + this.request['variableName.contains'] = $event; + this.getVariables(); + } + + createVariable() { + const modal = this.modalService.open(IrVariableCreateComponent, {centered: true}); + modal.componentInstance.actionCreated.subscribe(action => { + this.getVariables(); + }); + } + + editVariable(variable: IncidentVariableType) { + const modal = this.modalService.open(IrVariableCreateComponent, {centered: true}); + modal.componentInstance.incidentVariable = variable; + modal.componentInstance.actionCreated.subscribe(action => { + this.getVariables(); + }); + } + + getVariablePlaceHolder(variable: IncidentVariableType) { + return `$[variables.${variable.variableName}]`; + } + + delete(variable: IncidentVariableType) { + this.incidentResponseVariableService.delete(variable.id).subscribe(() => { + this.utmToastService.showSuccessBottom('Variable deleted successfully'); + this.getVariables(); + }, error1 => { + this.utmToastService.showError('Error', 'Error deleting variable'); + }); + } +} diff --git a/frontend/src/app/incident-response/incident-response-automation/incident-response-automation.component.html b/frontend/src/app/incident-response/incident-response-automation/incident-response-automation.component.html index 004daac0a..6b267eada 100644 --- a/frontend/src/app/incident-response/incident-response-automation/incident-response-automation.component.html +++ b/frontend/src/app/incident-response/incident-response-automation/incident-response-automation.component.html @@ -113,11 +113,12 @@
Incident Response Automation

{{rule.id }}

+ class="text-blue-800 font-size-base">{{ rule.id }}

{{rule.name }} + (click)="viewConditions = rule">{{ rule.name }} + Incident Response Automation

{{rule.description }}

+ class="font-size-base">{{ rule.description }}

{{rule.lastModifiedBy }}

+ class="font-size-base">{{ rule.lastModifiedBy }}

{{rule.lastModifiedDate | date:'short' }}

+ class="font-size-base">{{ rule.lastModifiedDate | date:'short' }}

- {{rule.conditions.length }} {{rule.conditions.length > 1 ? 'conditions' : 'condition'}} + {{ rule.conditions.length }} {{ rule.conditions.length > 1 ? 'conditions' : 'condition' }} - {{rule.command}} + {{ rule.command }} - {{rule.agentPlatform }} + {{ rule.agentPlatform }}
Incident Response Automation
- Rule {{viewConditions.name}} + Rule {{ viewConditions.name }}
@@ -54,7 +62,7 @@
- {{rulePrefix}} + {{ rulePrefix }}
+ ({{ 512 - formRule.get('description').value.length }}) + +
+ + + +
+ + + +
+ + +
+ + +
+
+
diff --git a/frontend/src/app/shared/components/utm/incident-variables/ir-variable-create/ir-variable-create.component.scss b/frontend/src/app/shared/components/utm/incident-variables/ir-variable-create/ir-variable-create.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/shared/components/utm/incident-variables/ir-variable-create/ir-variable-create.component.ts b/frontend/src/app/shared/components/utm/incident-variables/ir-variable-create/ir-variable-create.component.ts new file mode 100644 index 000000000..aad564daf --- /dev/null +++ b/frontend/src/app/shared/components/utm/incident-variables/ir-variable-create/ir-variable-create.component.ts @@ -0,0 +1,99 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; +import {debounceTime} from 'rxjs/operators'; +import {UtmToastService} from '../../../../alert/utm-toast.service'; +import {InputClassResolve} from '../../../../util/input-class-resolve'; +import {IncidentResponseVariableService} from '../../../../services/incidents/incident-response-variable.service'; +import {IncidentVariableType} from '../../../../types/incident/incident-variable.type'; + + +@Component({ + selector: 'app-ir-variable-create', + templateUrl: './ir-variable-create.component.html', + styleUrls: ['./ir-variable-create.component.scss'] +}) +export class IrVariableCreateComponent implements OnInit { + @Input() incidentVariable: IncidentVariableType; + incidentVariableForm: FormGroup; + creating = false; + @Output() actionCreated = new EventEmitter(); + exist = true; + typing = true; + + constructor(private formBuilder: FormBuilder, + public utmToastService: UtmToastService, + public activeModal: NgbActiveModal, + public inputClass: InputClassResolve, + private incidentResponseVariableService: IncidentResponseVariableService) { + } + + ngOnInit() { + this.incidentVariableForm = this.formBuilder.group({ + variableName: ['', [Validators.required, Validators.pattern('^[a-zA-Z0-9]*$')]], + variableValue: ['', Validators.required], + variableDescription: [''], + secret: [false, Validators.required], + id: [{value: null, disabled: true}] + }); + if (this.incidentVariable) { + this.exist = false; + this.incidentVariableForm.patchValue(this.incidentVariable); + this.incidentVariableForm.get('variableName').disable(); + } else { + this.incidentVariableForm.get('variableName').valueChanges.pipe(debounceTime(1000)).subscribe(value => { + this.searchRule(value); + }); + } + } + + createVariable() { + if (this.incidentVariable) { + this.editAction(); + } else { + this.createAction(); + } + } + + createAction() { + this.creating = true; + this.incidentResponseVariableService.create(this.incidentVariableForm.value).subscribe((response) => { + this.utmToastService.showSuccessBottom('Variable saved successfully'); + this.activeModal.close(); + this.creating = false; + this.actionCreated.emit(response.body); + }, error1 => { + this.creating = false; + this.utmToastService.showError('Error', 'Error saving variable'); + }); + } + + editAction() { + this.creating = true; + this.incidentResponseVariableService.update(this.incidentVariableForm.value).subscribe((response) => { + this.utmToastService.showSuccessBottom('Variable edited successfully'); + this.activeModal.close(); + this.creating = false; + this.actionCreated.emit(response.body); + }, error1 => { + this.creating = false; + this.utmToastService.showError('Error', 'Error editing Variable'); + }); + } + + searchRule(variableName: string) { + this.typing = true; + this.exist = true; + setTimeout(() => { + const req = { + 'variableName.equals': variableName + }; + this.incidentResponseVariableService.query(req).subscribe(response => { + this.exist = response.body.length > 0; + this.typing = false; + }); + }, 1000); + } + + +} diff --git a/frontend/src/app/shared/components/utm/util/utm-agent-console/utm-agent-console.component.html b/frontend/src/app/shared/components/utm/util/utm-agent-console/utm-agent-console.component.html index 4e17c5bdf..ae385a676 100644 --- a/frontend/src/app/shared/components/utm/util/utm-agent-console/utm-agent-console.component.html +++ b/frontend/src/app/shared/components/utm/util/utm-agent-console/utm-agent-console.component.html @@ -9,7 +9,7 @@ - {{agent.hostname}} ({{agent.os}}) + {{ agent.hostname }} ({{ agent.os }})