Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[backend] Apply the right platform & architecture when generating a scenario from OCTI (#1713) #2003

Merged
merged 19 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.openbas.migration;

import java.sql.Connection;
import java.sql.Statement;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.springframework.stereotype.Component;

@Component
public class V3_52__Sync_archi_names extends BaseJavaMigration {

@Override
public void migrate(Context context) throws Exception {
Connection connection = context.getConnection();
Statement statement = connection.createStatement();
statement.execute(
"UPDATE payloads SET payload_execution_arch = 'x86_64' WHERE payload_execution_arch = 'X86_64';");
savacano28 marked this conversation as resolved.
Show resolved Hide resolved
statement.execute(
"UPDATE payloads SET payload_execution_arch = 'arm64' WHERE payload_execution_arch = 'ARM64';");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand All @@ -30,17 +30,13 @@
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@Secured(ROLE_USER)
public class KillChainPhaseApi extends RestBehavior {

public static final String KILL_CHAIN_PHASE_URI = "/api/kill_chain_phases";

private KillChainPhaseRepository killChainPhaseRepository;

@Autowired
public void setKillChainPhaseRepository(KillChainPhaseRepository killChainPhaseRepository) {
this.killChainPhaseRepository = killChainPhaseRepository;
}
private final KillChainPhaseRepository killChainPhaseRepository;

@GetMapping("/api/kill_chain_phases")
public Iterable<KillChainPhase> killChainPhases() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.openbas.rest.payload;

import static io.openbas.database.model.Payload.PAYLOAD_EXECUTION_ARCH.ARM64;
import static io.openbas.database.model.Payload.PAYLOAD_EXECUTION_ARCH.X86_64;
import static io.openbas.database.model.Payload.PAYLOAD_EXECUTION_ARCH.arm64;
import static io.openbas.database.model.Payload.PAYLOAD_EXECUTION_ARCH.x86_64;
import static io.openbas.database.model.User.ROLE_ADMIN;
import static io.openbas.database.model.User.ROLE_USER;
import static io.openbas.helper.StreamHelper.fromIterable;
Expand Down Expand Up @@ -304,8 +304,8 @@ public static void validateArchitecture(String payloadType, Payload.PAYLOAD_EXEC
if (arch == null) {
throw new BadRequestException("Payload architecture cannot be null.");
}
if (Executable.EXECUTABLE_TYPE.equals(payloadType) && (arch != X86_64 && arch != ARM64)) {
throw new BadRequestException("Executable architecture must be x86_64 or ARM64.");
if (Executable.EXECUTABLE_TYPE.equals(payloadType) && (arch != x86_64 && arch != arm64)) {
throw new BadRequestException("Executable architecture must be x86_64 or arm64.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static java.util.Optional.ofNullable;

import io.openbas.database.model.Filters;
import io.openbas.database.model.Payload;
import io.openbas.utils.pagination.SearchPaginationInput;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -31,8 +32,8 @@ public static SearchPaginationInput handleArchitectureFilter(

filterOpt.ifPresent(
payloadFilter -> {
if (payloadFilter.getValues().contains("X86_64")
|| payloadFilter.getValues().contains("ARM64")) {
if (payloadFilter.getValues().contains(Payload.PAYLOAD_EXECUTION_ARCH.x86_64.name())
|| payloadFilter.getValues().contains(Payload.PAYLOAD_EXECUTION_ARCH.arm64.name())) {
payloadFilter.getValues().add(ALL_ARCHITECTURES);
}
});
Expand Down
203 changes: 203 additions & 0 deletions openbas-api/src/test/java/io/openbas/octi/OpenCTIApiTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package io.openbas.octi;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import io.openbas.opencti.OpenCTIApi;
import io.openbas.rest.attack_pattern.AttackPatternApi;
import io.openbas.rest.inject.InjectApi;
import io.openbas.rest.injector_contract.InjectorContractApi;
import io.openbas.rest.kill_chain_phase.KillChainPhaseApi;
import io.openbas.rest.scenario.ScenarioApi;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

/**
* Feature: OCTI OBAS - GENERATION SCENARIOS
*
* <p>Currently, OCTI uses the following endpoints to simulate scenarios from different types of
* entities such as (case incidents, groupings, reports, malwares, incidents, campaigns, intrusion
* sets, threat actor groups, threat actors individuals):
*
* <ul>
* <li>KillChainPhasesApi -> killChainPhases [GET: /api/kill-chain-phases/]
* <li>AttackPatternApi -> attackPatterns [GET: /api/attack_patterns/]
* <li>AttackPatternApi -> injectorContracts
* [GET:/api/attack_patterns/{attackPatternId}/injector_contracts]
* <li>InjectorContractApi -> injectorContracts [POST: /api/injector_contracts/search]
* <li>ScenarioApi -> creationScenario [POST: /api/scenarios/]
* <li>InjectApi -> createInjectForScenario [POST: /api/injects/{scenarioId}/injects]
* <li>OpenCTIApi -> latestExerciseByExternalId
* [GET:/api/opencti/v1/exercises/latest/{externalReferenceId}]
* </ul>
*
* <p>These non-regression tests will help us maintain control over any modifications to these
* endpoints.
*/
@ExtendWith(MockitoExtension.class)
class OpenCTIApiTest {

@Mock InjectApi injectApi;
@Mock ScenarioApi scenarioApi;
@Mock KillChainPhaseApi killChainPhaseApi;
@Mock AttackPatternApi attackPatternApi;
@Mock InjectorContractApi injectorContractApi;
@Mock OpenCTIApi openCTIApi;

private MockMvc mockMvc;

@BeforeEach
public void setUp() {
// Initialize mocks before each test
MockitoAnnotations.openMocks(this);

// Set up MockMvc with controllers
mockMvc =
MockMvcBuilders.standaloneSetup(
killChainPhaseApi,
attackPatternApi,
injectorContractApi,
scenarioApi,
injectApi,
openCTIApi)
.build();
}

// -- KILL CHAIN PHASES --
@Test
@DisplayName("Test to validate existence of the KillChainPhases endpoint")
public void testGetKillChainPhases_Sucess() throws Exception {
// -- EXECUTE --
mockMvc
.perform(get("/api/kill_chain_phases").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

// -- ATTACK PATTERNS --
@Test
@DisplayName("Test to validate existence of the AttackPatterns endpoint")
public void testGetAttackPatterns_Sucess() throws Exception {
// -- EXECUTE --
mockMvc
.perform(get("/api/attack_patterns").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

// -- INJECTOR CONTRACTS --
@Test
@DisplayName("Test to validate existence of the InjectorContracts endpoint")
public void testGetInjectorContracts_Success() throws Exception {
// -- PREPARE --
String attackPatternId = "attackPatternId";
// -- EXECUTE --
mockMvc
.perform(
get("/api/attack_patterns/{attackPatternId}/injector_contracts", attackPatternId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

@Test
@DisplayName(
"Test to validate existence of the InjectorContracts Search endpoint and validate SearchInput")
public void testSearchInjectorContracts_ValidInput() throws Exception {
// -- PREPARE --
String jsonInput =
"{"
+ "\"page\": 0, "
+ "\"size\": 100, "
+ "\"filterGroup\": {"
+ " \"filters\": ["
+ " {\"key\": \"injector_contract_attack_patterns\", \"operator\": \"contains\", \"values\": [\"attackPatternId\"]},"
+ " {\"key\": \"injector_contract_platforms\", \"operator\": \"contains\", \"values\": [\"platform1\", \"platform2\"]},"
+ " {\"key\": \"injector_contract_arch\", \"operator\": \"eq\", \"values\": [\"arm64\"]}"
+ " ],"
+ " \"mode\": \"and\""
+ "},"
+ "\"textSearch\": \"searchText\", "
+ "\"sorts\": [{\"property\": \"label\", \"direction\": \"asc\"}]"
+ "}";
// -- EXECUTE --
mockMvc
.perform(
post("/api/injector_contracts/search")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonInput))
.andExpect(status().isOk());
}

// -- SCENARIO --
@Test
@DisplayName(
"Test to validate existence of the 'Creation Scenario' endpoint and validate ScenarioInput")
public void testCreateScenario_ValidInput() throws Exception {
// -- PREPARE --
String jsonInput =
"{"
+ "\"scenario_name\": \"Test Scenario\", "
+ "\"scenario_subtitle\": \"Test Subtitle\", "
+ "\"scenario_description\": \"This is a test scenario.\", "
+ "\"scenario_category\": \"Incident Response\", "
+ "\"scenario_main_focus\": \"incident-response\", "
+ "\"scenario_severity\": \"high\", "
+ "\"scenario_external_reference\": \"test-ref-123\", "
+ "\"scenario_external_url\": \"https://example.com/dashboard/analyses/reports/test-ref-123\", "
+ "\"scenario_tags\": [\"tag-id-1\", \"tag-id-2\"]"
+ "}";

// -- EXECUTE --
mockMvc
.perform(post("/api/scenarios").contentType(MediaType.APPLICATION_JSON).content(jsonInput))
.andExpect(status().isOk());
}

// -- INJECTS --
@Test
@DisplayName(
"Test to validate existence of 'Add Inject to Scenario' endpoint and validate InjectInput")
public void testCreateInjectForScenario_ValidInput() throws Exception {
// -- PREPARE --
String jsonInput =
"{"
+ "\"inject_title\": \"Valid Title\", "
+ "\"inject_type\": \"openbas_email\", "
+ "\"inject_injector_contract\": \"contract-id\", "
+ "\"inject_content\": null, "
+ "\"inject_depends_duration\": 100, "
+ "\"inject_tags\": [\"Tag1\"], "
+ "\"inject_additional_param\": \"additional_param\""
+ "}";

// -- EXECUTE --
mockMvc
.perform(
post("/api/scenarios/{scenarioId}/injects", "scenario-id")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonInput))
.andExpect(status().isOk());
}

// -- LAST EXERCISE BY EXTERNAL ID --
@Test
@DisplayName("Test to validate existence of the Get Last Exercise Result endpoint")
public void testGetLatestExerciseByExternalReference_Success() throws Exception {
// -- PREPARE --
String externalReferenceId = "valid-id";

// -- EXECUTE --
mockMvc
.perform(
get("/api/opencti/v1/exercises/latest/{externalReferenceId}", externalReferenceId)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ void given_filter_input_by_source_should_return_a_page_of_payloads_filter_by_sou
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.numberOfElements").value(1));
.andExpect(jsonPath("$.numberOfElements").value(3));
savacano28 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Expand Down
Loading
Loading