-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[backend] Implement CrowdStrike native executor (#1366)
- Loading branch information
1 parent
7a8a756
commit 11260e8
Showing
15 changed files
with
627 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
openbas-framework/src/main/java/io/openbas/executors/crowdstrike/CrowdStrikeExecutor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package io.openbas.executors.crowdstrike; | ||
|
||
import io.openbas.asset.EndpointService; | ||
import io.openbas.executors.crowdstrike.client.CrowdStrikeExecutorClient; | ||
import io.openbas.executors.crowdstrike.config.CrowdStrikeExecutorConfig; | ||
import io.openbas.executors.crowdstrike.service.CrowdStrikeExecutorContextService; | ||
import io.openbas.executors.crowdstrike.service.CrowdStrikeExecutorService; | ||
import io.openbas.integrations.ExecutorService; | ||
import io.openbas.integrations.InjectorService; | ||
import jakarta.annotation.PostConstruct; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.time.Duration; | ||
|
||
@RequiredArgsConstructor | ||
@Service | ||
public class CrowdStrikeExecutor { | ||
|
||
private final CrowdStrikeExecutorConfig config; | ||
private final ThreadPoolTaskScheduler taskScheduler; | ||
private final CrowdStrikeExecutorClient client; | ||
private final EndpointService endpointService; | ||
private final CrowdStrikeExecutorContextService crowdStrikeExecutorContextService; | ||
private final ExecutorService executorService; | ||
private final InjectorService injectorService; | ||
|
||
@PostConstruct | ||
public void init() { | ||
CrowdStrikeExecutorService service = | ||
new CrowdStrikeExecutorService( | ||
this.executorService, | ||
this.client, | ||
this.config, | ||
this.crowdStrikeExecutorContextService, | ||
this.endpointService, | ||
this.injectorService); | ||
if (this.config.isEnable()) { | ||
this.taskScheduler.scheduleAtFixedRate(service, Duration.ofSeconds(60)); | ||
} | ||
} | ||
} |
147 changes: 147 additions & 0 deletions
147
...work/src/main/java/io/openbas/executors/crowdstrike/client/CrowdStrikeExecutorClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package io.openbas.executors.crowdstrike.client; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.core.type.TypeReference; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import io.openbas.executors.crowdstrike.config.CrowdStrikeExecutorConfig; | ||
import io.openbas.executors.crowdstrike.model.Authentication; | ||
import io.openbas.executors.crowdstrike.model.CrowdStrikeSession; | ||
import io.openbas.executors.crowdstrike.model.ResourcesHosts; | ||
import io.openbas.executors.crowdstrike.model.ResourcesSession; | ||
import jakarta.validation.constraints.NotBlank; | ||
import jakarta.validation.constraints.NotNull; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.java.Log; | ||
import org.apache.hc.client5.http.ClientProtocolException; | ||
import org.apache.hc.client5.http.classic.methods.HttpGet; | ||
import org.apache.hc.client5.http.classic.methods.HttpPost; | ||
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; | ||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; | ||
import org.apache.hc.client5.http.impl.classic.HttpClients; | ||
import org.apache.hc.core5.http.NameValuePair; | ||
import org.apache.hc.core5.http.io.entity.EntityUtils; | ||
import org.apache.hc.core5.http.io.entity.StringEntity; | ||
import org.apache.hc.core5.http.message.BasicNameValuePair; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.io.IOException; | ||
import java.time.Instant; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.logging.Level; | ||
|
||
@RequiredArgsConstructor | ||
@Service | ||
@Log | ||
public class CrowdStrikeExecutorClient { | ||
|
||
private static final Integer AUTH_TIMEOUT = 300; | ||
private static final String OAUTH_URI = "/oauth2/token"; | ||
private static final String ENDPOINTS_URI = "/devices/combined/host-group-members/v1"; | ||
private static final String SESSION_URI = "/real-time-response/entities/sessions/v1"; | ||
private static final String REAL_TIME_RESPONSE_URI = "/real-time-response/entities/active-responder-command/v1"; | ||
|
||
private final CrowdStrikeExecutorConfig config; | ||
private final ObjectMapper objectMapper = new ObjectMapper(); | ||
|
||
private Instant lastAuthentication = Instant.now().minusSeconds(AUTH_TIMEOUT); | ||
private String token; | ||
|
||
// -- ENDPOINTS -- | ||
|
||
public ResourcesHosts devices() { | ||
try { | ||
String jsonResponse = this.get(ENDPOINTS_URI + "?id=" + this.config.getHostGroup()); | ||
return this.objectMapper.readValue(jsonResponse, new TypeReference<>() {}); | ||
} catch (JsonProcessingException e) { | ||
log.log(Level.SEVERE, "Failed to parse JSON response. Error: {}", e.getMessage()); | ||
throw new RuntimeException(e); | ||
} catch (IOException e) { | ||
log.log(Level.SEVERE, "I/O error occurred during API request. Error: {}", e.getMessage()); | ||
throw new RuntimeException(e); | ||
} catch (Exception e) { | ||
log.log(Level.SEVERE, "Unexpected error occurred. Error: {}", e.getMessage()); | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
public void executeAction(String deviceId, String scriptName, String command) { | ||
try { | ||
// Open remote session | ||
Map<String, Object> bodySession = new HashMap<>(); | ||
bodySession.put("device_id", deviceId); | ||
bodySession.put("queue_offline", false); | ||
String jsonSessionResponse = this.post(SESSION_URI, bodySession); | ||
ResourcesSession sessions = this.objectMapper.readValue(jsonSessionResponse, new TypeReference<>() {}); | ||
CrowdStrikeSession session = sessions.getResources().getFirst(); | ||
if( session == null ) { | ||
log.log(Level.SEVERE, "Cannot get the session on the selected device"); | ||
throw new RuntimeException("Cannot get the session on the selected device"); | ||
} | ||
// Execute the command | ||
Map<String, Object> bodyCommand = new HashMap<>(); | ||
bodyCommand.put("session_id", session.getSession_id()); | ||
bodyCommand.put("base_command", "runscript"); | ||
bodyCommand.put("command_string", "runscript -CloudFile=\"" + scriptName + "\" -CommandLine=```'{\"command\":\"" + command + "\"}'```"); | ||
this.post(REAL_TIME_RESPONSE_URI, bodyCommand); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
// -- PRIVATE -- | ||
|
||
private String get(@NotBlank final String uri) throws IOException { | ||
if( this.lastAuthentication.isBefore(Instant.now().minusSeconds(AUTH_TIMEOUT))) { | ||
this.authenticate(); | ||
} | ||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { | ||
HttpGet httpGet = new HttpGet(this.config.getApiUrl() + uri); | ||
// Headers | ||
httpGet.addHeader("Authorization", "Bearer " + this.token); | ||
return httpClient.execute(httpGet, response -> EntityUtils.toString(response.getEntity())); | ||
} catch (IOException e) { | ||
throw new ClientProtocolException("Unexpected response for request on: " + uri); | ||
} | ||
} | ||
|
||
private String post(@NotBlank final String uri, @NotNull final Map<String, Object> body) throws IOException { | ||
if( this.lastAuthentication.isBefore(Instant.now().minusSeconds(AUTH_TIMEOUT))) { | ||
this.authenticate(); | ||
} | ||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { | ||
HttpPost httpPost = new HttpPost(this.config.getApiUrl() + uri); | ||
// Headers | ||
httpPost.addHeader("Authorization", "Bearer " + this.token); | ||
httpPost.addHeader("content-type", "application/json"); | ||
// Body | ||
StringEntity entity = new StringEntity(this.objectMapper.writeValueAsString(body)); | ||
httpPost.setEntity(entity); | ||
return httpClient.execute(httpPost, response -> EntityUtils.toString(response.getEntity())); | ||
} catch (IOException e) { | ||
throw new ClientProtocolException("Unexpected response"); | ||
} | ||
} | ||
|
||
private void authenticate() throws IOException { | ||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { | ||
HttpPost httpPost = new HttpPost(this.config.getApiUrl() + OAUTH_URI); | ||
// Headers | ||
httpPost.addHeader("content-type", "application/x-www-form-urlencoded"); | ||
// Body | ||
List<NameValuePair> params = new ArrayList<>(); | ||
params.add(new BasicNameValuePair("client_id", this.config.getClientId())); | ||
params.add(new BasicNameValuePair("client_secret", this.config.getClientSecret())); | ||
params.add(new BasicNameValuePair("grant_type", "client_credentials")); | ||
httpPost.setEntity(new UrlEncodedFormEntity(params)); | ||
String jsonResponse = httpClient.execute(httpPost, response -> EntityUtils.toString(response.getEntity())); | ||
Authentication auth = this.objectMapper.readValue(jsonResponse, new TypeReference<>() {}); | ||
this.token = auth.getAccess_token(); | ||
this.lastAuthentication = Instant.now(); | ||
} catch (IOException e) { | ||
throw new ClientProtocolException("Unexpected response"); | ||
} | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
...work/src/main/java/io/openbas/executors/crowdstrike/config/CrowdStrikeExecutorConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package io.openbas.executors.crowdstrike.config; | ||
|
||
import jakarta.validation.constraints.NotBlank; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Setter | ||
@Component | ||
@ConfigurationProperties(prefix = "executor.crowdstrike") | ||
public class CrowdStrikeExecutorConfig { | ||
|
||
@Getter private boolean enable; | ||
|
||
@Getter @NotBlank private String id; | ||
|
||
@Getter @NotBlank private String apiUrl; | ||
|
||
@Getter @NotBlank private String clientId; | ||
|
||
@Getter @NotBlank private String clientSecret; | ||
|
||
@Getter @NotBlank private String hostGroup; | ||
|
||
@Getter @NotBlank private String windowsScriptName; | ||
|
||
@Getter @NotBlank private String unixScriptName; | ||
} |
11 changes: 11 additions & 0 deletions
11
openbas-framework/src/main/java/io/openbas/executors/crowdstrike/model/Authentication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package io.openbas.executors.crowdstrike.model; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import lombok.Data; | ||
|
||
@Data | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
public class Authentication { | ||
|
||
private String access_token; | ||
} |
21 changes: 21 additions & 0 deletions
21
...bas-framework/src/main/java/io/openbas/executors/crowdstrike/model/CrowdStrikeDevice.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package io.openbas.executors.crowdstrike.model; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import io.openbas.executors.tanium.model.Os; | ||
import io.openbas.executors.tanium.model.Processor; | ||
import lombok.Data; | ||
|
||
@Data | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
public class CrowdStrikeDevice { | ||
|
||
private String device_id; | ||
private String hostname; | ||
private String platform_name; | ||
private String os_version; | ||
private String external_ip; | ||
private String connection_ip; | ||
private String mac_address; | ||
private String os_product_name; | ||
private String last_seen; | ||
} |
14 changes: 14 additions & 0 deletions
14
...as-framework/src/main/java/io/openbas/executors/crowdstrike/model/CrowdStrikeSession.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package io.openbas.executors.crowdstrike.model; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import io.openbas.executors.tanium.model.Os; | ||
import io.openbas.executors.tanium.model.Processor; | ||
import lombok.Data; | ||
|
||
@Data | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
public class CrowdStrikeSession { | ||
|
||
private String session_id; | ||
private String device_id; | ||
} |
13 changes: 13 additions & 0 deletions
13
openbas-framework/src/main/java/io/openbas/executors/crowdstrike/model/ResourcesHosts.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package io.openbas.executors.crowdstrike.model; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import lombok.Data; | ||
|
||
import java.util.List; | ||
|
||
@Data | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
public class ResourcesHosts { | ||
|
||
private List<CrowdStrikeDevice> resources; | ||
} |
13 changes: 13 additions & 0 deletions
13
openbas-framework/src/main/java/io/openbas/executors/crowdstrike/model/ResourcesSession.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package io.openbas.executors.crowdstrike.model; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import lombok.Data; | ||
|
||
import java.util.List; | ||
|
||
@Data | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
public class ResourcesSession { | ||
|
||
private List<CrowdStrikeSession> resources; | ||
} |
Oops, something went wrong.