Skip to content
Open
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package io.openaev.execution;

import static io.openaev.executors.crowdstrike.service.CrowdStrikeExecutorService.CROWDSTRIKE_EXECUTOR_NAME;
import static io.openaev.executors.crowdstrike.service.CrowdStrikeExecutorService.CROWDSTRIKE_EXECUTOR_TYPE;
import static io.openaev.executors.sentinelone.service.SentinelOneExecutorService.SENTINELONE_EXECUTOR_NAME;
import static io.openaev.executors.sentinelone.service.SentinelOneExecutorService.SENTINELONE_EXECUTOR_TYPE;
import static io.openaev.executors.tanium.service.TaniumExecutorService.TANIUM_EXECUTOR_NAME;
import static io.openaev.executors.tanium.service.TaniumExecutorService.TANIUM_EXECUTOR_TYPE;

import com.google.common.annotations.VisibleForTesting;
import io.openaev.database.model.*;
Expand Down Expand Up @@ -46,42 +50,29 @@ public void launchExecutorContext(Inject inject) {
agents.removeAll(inactiveAgents);
Set<Agent> agentsWithoutExecutor = executorUtils.foundAgentsWithoutExecutor(agents);
agents.removeAll(agentsWithoutExecutor);
Set<Agent> crowdstrikeAgents = executorUtils.foundCrowdstrikeAgents(agents);
Set<Agent> crowdstrikeAgents =
executorUtils.foundAgentsByExecutorType(agents, CROWDSTRIKE_EXECUTOR_TYPE);
agents.removeAll(crowdstrikeAgents);
Set<Agent> sentineloneAgents = executorUtils.foundSentineloneAgents(agents);
Set<Agent> sentineloneAgents =
executorUtils.foundAgentsByExecutorType(agents, SENTINELONE_EXECUTOR_TYPE);
agents.removeAll(sentineloneAgents);
Set<Agent> taniumAgents = executorUtils.foundAgentsByExecutorType(agents, TANIUM_EXECUTOR_TYPE);
agents.removeAll(taniumAgents);

AtomicBoolean atLeastOneExecution = new AtomicBoolean(false);
// Manage inactive agents
saveInactiveAgentsTraces(inactiveAgents, injectStatus);
// Manage without executor agents
saveWithoutExecutorAgentsTraces(agentsWithoutExecutor, injectStatus);
// Manage Crowdstrike agents for batch execution
if (!crowdstrikeAgents.isEmpty()) {
try {
ExecutorContextService executorContextService =
context.getBean(CROWDSTRIKE_EXECUTOR_NAME, ExecutorContextService.class);
executorContextService.launchBatchExecutorSubprocess(
inject, crowdstrikeAgents, injectStatus);
atLeastOneExecution.set(true);
} catch (Exception e) {
log.error("Crowdstrike launchBatchExecutorSubprocess error: {}", e.getMessage());
saveAgentsErrorTraces(e, crowdstrikeAgents, injectStatus);
}
}
launchBatchExecutorContextForAgent(
crowdstrikeAgents, CROWDSTRIKE_EXECUTOR_NAME, inject, injectStatus, atLeastOneExecution);
// Manage Sentinelone agents for batch execution
if (!sentineloneAgents.isEmpty()) {
try {
ExecutorContextService executorContextService =
context.getBean(SENTINELONE_EXECUTOR_NAME, ExecutorContextService.class);
executorContextService.launchBatchExecutorSubprocess(
inject, sentineloneAgents, injectStatus);
atLeastOneExecution.set(true);
} catch (Exception e) {
log.error("Sentinelone launchBatchExecutorSubprocess error: {}", e.getMessage());
saveAgentsErrorTraces(e, sentineloneAgents, injectStatus);
}
}
launchBatchExecutorContextForAgent(
sentineloneAgents, SENTINELONE_EXECUTOR_NAME, inject, injectStatus, atLeastOneExecution);
// Manage Tanium agents for batch execution
launchBatchExecutorContextForAgent(
taniumAgents, TANIUM_EXECUTOR_NAME, inject, injectStatus, atLeastOneExecution);
// Manage remaining agents
agents.forEach(
agent -> {
Expand All @@ -98,6 +89,25 @@ public void launchExecutorContext(Inject inject) {
}
}

private void launchBatchExecutorContextForAgent(
Set<Agent> agents,
String executorName,
Inject inject,
InjectStatus injectStatus,
AtomicBoolean atLeastOneExecution) {
if (!agents.isEmpty()) {
try {
ExecutorContextService executorContextService =
context.getBean(executorName, ExecutorContextService.class);
executorContextService.launchBatchExecutorSubprocess(inject, agents, injectStatus);
atLeastOneExecution.set(true);
} catch (Exception e) {
log.error("{} launchBatchExecutorSubprocess error: {}", executorName, e.getMessage());
saveAgentsErrorTraces(e, agents, injectStatus);
}
}
}

@VisibleForTesting
public void saveAgentErrorTrace(AgentException e, InjectStatus injectStatus) {
executionTraceRepository.save(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ public abstract void launchExecutorSubprocess(Inject inject, Endpoint assetEndpo
* @throws InterruptedException if problem
*/
public abstract List<Agent> launchBatchExecutorSubprocess(
Inject inject, Set<Agent> agents, InjectStatus injectStatus) throws InterruptedException;
Inject inject, Set<Agent> agents, InjectStatus injectStatus);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.openaev.executors.crowdstrike;

import io.openaev.executors.crowdstrike.client.CrowdStrikeExecutorClient;
import io.openaev.executors.crowdstrike.config.CrowdStrikeExecutorConfig;
import io.openaev.executors.crowdstrike.service.CrowdStrikeExecutorContextService;
import io.openaev.executors.crowdstrike.service.CrowdStrikeGarbageCollectorService;
import io.openaev.service.AgentService;
import jakarta.annotation.PostConstruct;
Expand All @@ -18,15 +18,17 @@ public class CrowdStrikeGarbageCollector {

private final CrowdStrikeExecutorConfig config;
private final ThreadPoolTaskScheduler taskScheduler;
private final CrowdStrikeExecutorClient client;
private final CrowdStrikeExecutorContextService crowdStrikeExecutorContextService;
private final AgentService agentService;

@PostConstruct
public void init() {
if (this.config.isEnable()) {
CrowdStrikeGarbageCollectorService service =
new CrowdStrikeGarbageCollectorService(this.config, this.client, this.agentService);
this.taskScheduler.scheduleAtFixedRate(service, Duration.ofHours(6));
new CrowdStrikeGarbageCollectorService(
this.config, this.crowdStrikeExecutorContextService, this.agentService);
this.taskScheduler.scheduleAtFixedRate(
service, Duration.ofHours(this.config.getCleanImplantInterval()));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.openaev.executors.crowdstrike.client;

import static io.openaev.executors.crowdstrike.service.CrowdStrikeExecutorService.CROWDSTRIKE_EXECUTOR_NAME;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.openaev.authorisation.HttpClientFactory;
Expand All @@ -8,6 +10,7 @@
import io.openaev.executors.crowdstrike.model.Authentication;
import io.openaev.executors.crowdstrike.model.ResourcesHosts;
import io.openaev.executors.crowdstrike.model.ResourcesSession;
import io.openaev.executors.exception.ExecutorException;
import io.openaev.service.EndpointService;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
Expand Down Expand Up @@ -83,7 +86,7 @@ public List<CrowdStrikeDevice> devices(String hostGroup) {
return hosts;
} catch (Exception e) {
log.error(String.format("Unexpected error occurred. Error: %s", e.getMessage()), e);
throw new RuntimeException(e);
throw new ExecutorException(e, e.getMessage(), CROWDSTRIKE_EXECUTOR_NAME);
}
}

Expand Down Expand Up @@ -130,7 +133,7 @@ private ResourcesHosts getResourcesHosts(int offset, String hostGroup) {
"Error occurred during Crowdstrike getResourcesHosts API request. Error: %s",
e.getMessage()),
e);
throw new RuntimeException(e);
throw new ExecutorException(e, e.getMessage(), CROWDSTRIKE_EXECUTOR_NAME);
}
}

Expand All @@ -144,7 +147,7 @@ public ResourcesGroups hostGroup(String hostGroup) {
String.format(
"Error occurred during Crowdstrike hostGroup API request. Error: %s", e.getMessage()),
e);
throw new RuntimeException(e);
throw new ExecutorException(e, e.getMessage(), CROWDSTRIKE_EXECUTOR_NAME);
}
}

Expand All @@ -159,7 +162,8 @@ public void executeAction(List<String> devicesId, String scriptName, String comm
this.objectMapper.readValue(jsonSessionResponse, new TypeReference<>() {});
if (session == null) {
log.error("Cannot get the session on the selected device");
throw new RuntimeException("Cannot get the session on the selected device");
throw new ExecutorException(
"Cannot get the session on the selected device", CROWDSTRIKE_EXECUTOR_NAME);
}
// Execute the command
Map<String, Object> bodyCommand = new HashMap<>();
Expand All @@ -174,7 +178,7 @@ public void executeAction(List<String> devicesId, String scriptName, String comm
+ "\"}'```");
this.postAsync(REAL_TIME_RESPONSE_URI, bodyCommand);
} catch (IOException e) {
throw new RuntimeException(e);
throw new ExecutorException(e, e.getMessage(), CROWDSTRIKE_EXECUTOR_NAME);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public class CrowdStrikeExecutorConfig {

@Getter @NotBlank private Integer apiRegisterInterval = 1200;

@Getter @NotBlank private Integer cleanImplantInterval = 8;

@Getter @NotBlank private String clientId;

@Getter @NotBlank private String clientSecret;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
import io.openaev.executors.crowdstrike.client.CrowdStrikeExecutorClient;
import io.openaev.executors.crowdstrike.config.CrowdStrikeExecutorConfig;
import io.openaev.executors.crowdstrike.model.CrowdStrikeAction;
import io.openaev.executors.exception.ExecutorException;
import jakarta.validation.constraints.NotNull;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -28,8 +32,6 @@
public class CrowdStrikeExecutorContextService extends ExecutorContextService {
public static final String SERVICE_NAME = CROWDSTRIKE_EXECUTOR_NAME;

private static final int SLEEP_INTERVAL_BATCH_EXECUTIONS = 1000;

private static final String AGENT_ID_VARIABLE = "$agentID";
private static final String ARCH_VARIABLE = "$architecture";

Expand All @@ -49,6 +51,8 @@ public class CrowdStrikeExecutorContextService extends ExecutorContextService {
private final LicenseCacheManager licenseCacheManager;
private final ExecutorService executorService;

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

@Override
public void launchExecutorSubprocess(
@NotNull final Inject inject,
Expand All @@ -57,13 +61,14 @@ public void launchExecutorSubprocess(

@Override
public List<Agent> launchBatchExecutorSubprocess(
Inject inject, Set<Agent> agents, InjectStatus injectStatus) throws InterruptedException {
Inject inject, Set<Agent> agents, InjectStatus injectStatus) {

eeService.throwEEExecutorService(
licenseCacheManager.getEnterpriseEditionInfo(), SERVICE_NAME, injectStatus);

if (!this.crowdStrikeExecutorConfig.isEnable()) {
throw new RuntimeException("Fatal error: CrowdStrike executor is not enabled");
throw new ExecutorException(
"Fatal error: CrowdStrike executor is not enabled", CROWDSTRIKE_EXECUTOR_NAME);
}
List<Agent> csAgents = new ArrayList<>(agents);

Expand Down Expand Up @@ -96,29 +101,24 @@ public List<Agent> launchBatchExecutorSubprocess(
return csAgents;
}

private void executeActions(List<CrowdStrikeAction> actions) throws InterruptedException {
public void executeActions(List<CrowdStrikeAction> actions) {
int paginationLimit = this.crowdStrikeExecutorConfig.getApiBatchExecutionActionPagination();
for (CrowdStrikeAction action : actions) {
int paginationLimit = this.crowdStrikeExecutorConfig.getApiBatchExecutionActionPagination();
// Pagination with 1s wait if needed because each implant will call OpenAEV API to set traces
if (action.getAgents().size() > paginationLimit) {
int numberOfExecution = Math.ceilDiv(action.getAgents().size(), paginationLimit);
int fromIndex = 0;
int toIndex = paginationLimit;
for (int callNumber = 0; callNumber < numberOfExecution; callNumber += 1) {
this.crowdStrikeExecutorClient.executeAction(
action.getAgents().subList(fromIndex, toIndex).stream().map(Agent::getId).toList(),
action.getScriptName(),
action.getCommandEncoded());
fromIndex = toIndex;
toIndex = Math.min(action.getAgents().size(), fromIndex + paginationLimit);
Thread.sleep(SLEEP_INTERVAL_BATCH_EXECUTIONS);
}
} else {
this.crowdStrikeExecutorClient.executeAction(
action.getAgents().stream().map(Agent::getId).toList(),
action.getScriptName(),
action.getCommandEncoded());
Thread.sleep(SLEEP_INTERVAL_BATCH_EXECUTIONS);
int paginationCount = (int) Math.ceil(action.getAgents().size() / (double) paginationLimit);
for (int batchIndex = 0; batchIndex < paginationCount; batchIndex++) {
int fromIndex = (batchIndex * paginationLimit);
int toIndex = Math.min(fromIndex + paginationLimit, action.getAgents().size());
List<String> batchAgentIds =
action.getAgents().subList(fromIndex, toIndex).stream().map(Agent::getId).toList();
// Pagination of XXX agents (paginationLimit) per batch with 5s waiting
// because each XXX actions will call the CS API to execute the implants
// and each implant will call OpenAEV API to set traces
scheduledExecutorService.schedule(
() ->
this.crowdStrikeExecutorClient.executeAction(
batchAgentIds, action.getScriptName(), action.getCommandEncoded()),
batchIndex * 5L,
TimeUnit.SECONDS);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import static io.openaev.executors.ExecutorHelper.UNIX_CLEAN_PAYLOADS_COMMAND;
import static io.openaev.executors.ExecutorHelper.WINDOWS_CLEAN_PAYLOADS_COMMAND;
import static io.openaev.executors.utils.ExecutorUtils.getAgentsFromOS;

import io.openaev.database.model.Agent;
import io.openaev.database.model.Endpoint;
import io.openaev.executors.crowdstrike.client.CrowdStrikeExecutorClient;
import io.openaev.executors.crowdstrike.config.CrowdStrikeExecutorConfig;
import io.openaev.executors.crowdstrike.model.CrowdStrikeAction;
import io.openaev.service.AgentService;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -19,49 +22,51 @@
public class CrowdStrikeGarbageCollectorService implements Runnable {

private final CrowdStrikeExecutorConfig config;
private final CrowdStrikeExecutorClient client;
private final CrowdStrikeExecutorContextService crowdStrikeExecutorContextService;
private final AgentService agentService;

@Autowired
public CrowdStrikeGarbageCollectorService(
CrowdStrikeExecutorConfig config,
CrowdStrikeExecutorClient client,
CrowdStrikeExecutorContextService crowdStrikeExecutorContextService,
AgentService agentService) {
this.config = config;
this.client = client;
this.crowdStrikeExecutorContextService = crowdStrikeExecutorContextService;
this.agentService = agentService;
}

@Override
public void run() {
log.info("Running CrowdStrike executor garbage collector...");
List<io.openaev.database.model.Agent> agents =
List<Agent> agents =
this.agentService.getAgentsByExecutorType(
CrowdStrikeExecutorService.CROWDSTRIKE_EXECUTOR_TYPE);
log.info("Running CrowdStrike executor garbage collector on " + agents.size() + " agents");
agents.forEach(
agent -> {
Endpoint endpoint = (Endpoint) agent.getAsset();
switch (endpoint.getPlatform()) {
case Windows -> {
log.info("Sending Windows command line to " + endpoint.getName());
this.client.executeAction(
List.of(agent.getExternalReference()),
this.config.getWindowsScriptName(),
Base64.getEncoder()
.encodeToString(
WINDOWS_CLEAN_PAYLOADS_COMMAND.getBytes(StandardCharsets.UTF_16LE)));
}
case Linux, MacOS -> {
log.info("Sending Unix command line to " + endpoint.getName());
this.client.executeAction(
List.of(agent.getExternalReference()),
this.config.getUnixScriptName(),
Base64.getEncoder()
.encodeToString(
UNIX_CLEAN_PAYLOADS_COMMAND.getBytes(StandardCharsets.UTF_8)));
}
}
});
if (!agents.isEmpty()) {
List<CrowdStrikeAction> actions = new ArrayList<>();
log.info("Running CrowdStrike executor garbage collector on " + agents.size() + " agents");
List<Agent> windowsAgents = getAgentsFromOS(agents, Endpoint.PLATFORM_TYPE.Windows);
if (!windowsAgents.isEmpty()) {
CrowdStrikeAction action = new CrowdStrikeAction();
action.setAgents(windowsAgents);
action.setScriptName(this.config.getWindowsScriptName());
action.setCommandEncoded(
Base64.getEncoder()
.encodeToString(
WINDOWS_CLEAN_PAYLOADS_COMMAND.getBytes(StandardCharsets.UTF_16LE)));
actions.add(action);
}
List<Agent> unixAgents = new ArrayList<>();
unixAgents.addAll(getAgentsFromOS(agents, Endpoint.PLATFORM_TYPE.Linux));
unixAgents.addAll(getAgentsFromOS(agents, Endpoint.PLATFORM_TYPE.MacOS));
if (!unixAgents.isEmpty()) {
CrowdStrikeAction action = new CrowdStrikeAction();
action.setAgents(unixAgents);
action.setScriptName(this.config.getUnixScriptName());
action.setCommandEncoded(
Base64.getEncoder()
.encodeToString(UNIX_CLEAN_PAYLOADS_COMMAND.getBytes(StandardCharsets.UTF_8)));
actions.add(action);
}
crowdStrikeExecutorContextService.executeActions(actions);
}
}
}
Loading