Skip to content

Commit aaf48a6

Browse files
nsgupta1Davidding4718
authored andcommitted
Initial commit for CrowdStrike source crawler (opensearch-project#5619)
* Initial commit for CrowdStrike source crawler Signed-off-by: nsgupta1 <nsgupta1@users.noreply.github.com> --------- Signed-off-by: ngsupta1 <guptaneha.e@gmail.com> Signed-off-by: nsgupta1 <nsgupta1@users.noreply.github.com> Co-authored-by: nsgupta1 <nsgupta1@users.noreply.github.com>
1 parent 96fd03a commit aaf48a6

13 files changed

Lines changed: 556 additions & 1 deletion

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
plugins {
2+
id 'java'
3+
}
4+
5+
dependencies {
6+
7+
implementation project(path: ':data-prepper-plugins:saas-source-plugins:source-crawler')
8+
implementation project(path: ':data-prepper-api')
9+
implementation project(path: ':data-prepper-plugins:aws-plugin-api')
10+
implementation project(path: ':data-prepper-plugins:buffer-common')
11+
implementation project(path: ':data-prepper-plugins:common')
12+
13+
implementation libs.commons.io
14+
implementation 'io.micrometer:micrometer-core'
15+
implementation 'com.fasterxml.jackson.core:jackson-core'
16+
implementation 'com.fasterxml.jackson.core:jackson-databind'
17+
implementation 'javax.inject:javax.inject:1'
18+
19+
20+
implementation 'org.projectlombok:lombok:1.18.30'
21+
annotationProcessor 'org.projectlombok:lombok:1.18.30'
22+
23+
testImplementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.4'
24+
testImplementation project(path: ':data-prepper-test-common')
25+
testImplementation 'ch.qos.logback:logback-classic:1.4.12'
26+
testImplementation 'org.slf4j:slf4j-api:2.0.7'
27+
implementation(libs.spring.context) {
28+
exclude group: 'commons-logging', module: 'commons-logging'
29+
}
30+
implementation(libs.spring.web)
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.opensearch.dataprepper.plugins.source.crowdstrike;
2+
3+
import org.opensearch.dataprepper.model.acknowledgements.AcknowledgementSet;
4+
import org.opensearch.dataprepper.model.buffer.Buffer;
5+
import org.opensearch.dataprepper.model.event.Event;
6+
import org.opensearch.dataprepper.model.record.Record;
7+
import org.opensearch.dataprepper.plugins.source.source_crawler.base.CrawlerClient;
8+
import org.opensearch.dataprepper.plugins.source.source_crawler.coordination.state.SaasWorkerProgressState;
9+
import org.opensearch.dataprepper.plugins.source.source_crawler.model.ItemInfo;
10+
import javax.inject.Named;
11+
import java.time.Instant;
12+
import java.util.Iterator;
13+
14+
/**
15+
* This class represents a CrowdStrike client.
16+
*/
17+
@Named
18+
public class CrowdStrikeClient implements CrawlerClient {
19+
20+
@Override
21+
public Iterator<ItemInfo> listItems(Instant lastPollTime) {
22+
return null;
23+
}
24+
25+
@Override
26+
public void executePartition(SaasWorkerProgressState state, Buffer<Record<Event>> buffer, AcknowledgementSet acknowledgementSet) {
27+
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.opensearch.dataprepper.plugins.source.crowdstrike;
2+
3+
4+
import com.google.common.annotations.Beta;
5+
import org.opensearch.dataprepper.metrics.PluginMetrics;
6+
import org.opensearch.dataprepper.model.acknowledgements.AcknowledgementSetManager;
7+
import org.opensearch.dataprepper.model.annotations.DataPrepperPlugin;
8+
import org.opensearch.dataprepper.model.annotations.DataPrepperPluginConstructor;
9+
import org.opensearch.dataprepper.model.buffer.Buffer;
10+
import org.opensearch.dataprepper.model.event.Event;
11+
import org.opensearch.dataprepper.model.plugin.PluginFactory;
12+
import org.opensearch.dataprepper.model.record.Record;
13+
import org.opensearch.dataprepper.model.source.Source;
14+
import org.opensearch.dataprepper.plugins.source.crowdstrike.rest.CrowdStrikeAuthClient;
15+
import org.opensearch.dataprepper.plugins.source.source_crawler.CrawlerApplicationContextMarker;
16+
import org.opensearch.dataprepper.plugins.source.source_crawler.base.Crawler;
17+
import org.opensearch.dataprepper.plugins.source.source_crawler.base.CrawlerSourcePlugin;
18+
import org.opensearch.dataprepper.plugins.source.source_crawler.base.PluginExecutorServiceProvider;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
import static org.opensearch.dataprepper.plugins.source.crowdstrike.utils.Constants.PLUGIN_NAME;
23+
24+
25+
/**
26+
* CrowdStrike connector entry point.
27+
* 🚧 Work in progress — under active development.
28+
* Not ready for production use.
29+
*/
30+
@Beta
31+
@DataPrepperPlugin(name = PLUGIN_NAME,
32+
pluginType = Source.class,
33+
pluginConfigurationType = CrowdStrikeSourceConfig.class,
34+
packagesToScan = {CrawlerApplicationContextMarker.class, CrowdStrikeSource.class}
35+
)
36+
public class CrowdStrikeSource extends CrawlerSourcePlugin {
37+
38+
private static final Logger log = LoggerFactory.getLogger(CrowdStrikeSource.class);
39+
private final CrowdStrikeSourceConfig sourceConfig;
40+
private final CrowdStrikeAuthClient authClient;
41+
42+
@DataPrepperPluginConstructor
43+
public CrowdStrikeSource(final CrowdStrikeSourceConfig sourceConfig,
44+
final PluginMetrics pluginMetrics,
45+
final PluginFactory pluginFactory,
46+
final AcknowledgementSetManager acknowledgementSetManager,
47+
final CrowdStrikeAuthClient authClient,
48+
Crawler crawler, PluginExecutorServiceProvider executorServiceProvider) {
49+
super(PLUGIN_NAME, pluginMetrics, sourceConfig, pluginFactory, acknowledgementSetManager, crawler, executorServiceProvider);
50+
log.info("Creating CrowdStrike Source Plugin");
51+
this.sourceConfig = sourceConfig;
52+
this.authClient = authClient;
53+
}
54+
55+
@Override
56+
public void start(Buffer<Record<Event>> buffer) {
57+
log.info("Starting CrowdStrike Source Plugin...");
58+
authClient.initCredentials();
59+
// super.start(buffer);
60+
}
61+
62+
@Override
63+
public void stop() {
64+
log.info("Stopping CrowdStrike Source Plugin...");
65+
super.stop();
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.opensearch.dataprepper.plugins.source.crowdstrike;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import jakarta.validation.Valid;
5+
import jakarta.validation.constraints.Max;
6+
import jakarta.validation.constraints.Min;
7+
import lombok.Getter;
8+
import org.opensearch.dataprepper.plugins.source.crowdstrike.configuration.AuthenticationConfig;
9+
import org.opensearch.dataprepper.plugins.source.source_crawler.base.CrawlerSourceConfig;
10+
11+
/**
12+
* Configuration class for the CrowdStrike source plugin.
13+
*/
14+
@Getter
15+
public class CrowdStrikeSourceConfig implements CrawlerSourceConfig {
16+
17+
private static final int DEFAULT_NUMBER_OF_WORKERS = 5;
18+
19+
@JsonProperty("authentication")
20+
@Valid
21+
protected AuthenticationConfig authenticationConfig;
22+
23+
@JsonProperty("acknowledgments")
24+
private boolean acknowledgments = false;
25+
26+
@JsonProperty("workers")
27+
@Min(1)
28+
@Max(50)
29+
@Valid
30+
private int numWorkers = DEFAULT_NUMBER_OF_WORKERS;
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.opensearch.dataprepper.plugins.source.crowdstrike.configuration;
2+
3+
import jakarta.validation.constraints.AssertTrue;
4+
import lombok.Getter;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
/**
8+
* Configuration class for authentication with the CrowdStrike API.
9+
*/
10+
@Getter
11+
public class AuthenticationConfig {
12+
13+
@JsonProperty("client_id")
14+
private String clientId;
15+
16+
@JsonProperty("client_secret")
17+
private String clientSecret;
18+
19+
@AssertTrue(message = "Client Id and Client Secret are both required for Authentication")
20+
public boolean isValidConfig() {
21+
return clientId != null && clientSecret != null;
22+
}
23+
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package org.opensearch.dataprepper.plugins.source.crowdstrike.rest;
2+
3+
import lombok.Getter;
4+
import org.opensearch.dataprepper.plugins.source.crowdstrike.CrowdStrikeSourceConfig;
5+
import org.opensearch.dataprepper.plugins.source.crowdstrike.configuration.AuthenticationConfig;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.springframework.http.HttpEntity;
9+
import org.springframework.http.HttpHeaders;
10+
import org.springframework.http.HttpStatus;
11+
import org.springframework.http.MediaType;
12+
import org.springframework.http.ResponseEntity;
13+
import org.springframework.web.client.HttpClientErrorException;
14+
import org.springframework.web.client.RestTemplate;
15+
import java.time.Instant;
16+
import java.util.Map;
17+
import javax.inject.Named;
18+
import static org.opensearch.dataprepper.logging.DataPrepperMarkers.NOISY;
19+
20+
21+
/**
22+
* Client to manage authentication with the CrowdStrike API.
23+
* Responsible for acquiring and refreshing Bearer tokens to access
24+
* CrowdStrike services.
25+
*/
26+
@Named
27+
public class CrowdStrikeAuthClient {
28+
29+
@Getter
30+
private String bearerToken;
31+
@Getter
32+
private Instant expireTime;
33+
private final String clientId;
34+
private final String clientSecret;
35+
RestTemplate restTemplate = new RestTemplate();
36+
private static final Logger log = LoggerFactory.getLogger(CrowdStrikeAuthClient.class);
37+
private static final String OAUTH_TOKEN_URL = "https://api.crowdstrike.com/oauth2/token";
38+
private static final String ACCESS_TOKEN = "access_token";
39+
private static final String EXPIRE_IN = "expires_in";
40+
41+
42+
43+
public CrowdStrikeAuthClient(final CrowdStrikeSourceConfig sourceConfig) {
44+
AuthenticationConfig authConfig = sourceConfig.getAuthenticationConfig();
45+
this.clientId = authConfig.getClientId();
46+
this.clientSecret = authConfig.getClientSecret();
47+
}
48+
49+
50+
/**
51+
* Initializes the credentials by obtaining an authentication token.
52+
*/
53+
public void initCredentials() {
54+
log.info("Getting CrowdStrike Authentication Token");
55+
getAuthToken();
56+
}
57+
58+
/**
59+
* Retrieves a new authentication token from the CrowdStrike API.
60+
* The token is stored in the {@code bearerToken} field, and its expiration time is updated.
61+
*
62+
* @throws RuntimeException if the token cannot be retrieved.
63+
*/
64+
private void getAuthToken() {
65+
log.info(NOISY, "You are trying to access token");
66+
HttpHeaders headers = new HttpHeaders();
67+
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
68+
headers.setBasicAuth(this.clientId, this.clientSecret);
69+
HttpEntity<String> request = new HttpEntity<>(headers);
70+
try {
71+
ResponseEntity<Map> response = restTemplate.postForEntity(OAUTH_TOKEN_URL, request, Map.class);
72+
Map tokenData = response.getBody();
73+
this.bearerToken = (String) tokenData.get(ACCESS_TOKEN);
74+
this.expireTime = Instant.now().plusSeconds((Integer) tokenData.get(EXPIRE_IN));
75+
log.info("Access token acquired successfully");
76+
} catch (HttpClientErrorException ex) {
77+
this.expireTime = Instant.ofEpochMilli(0);
78+
HttpStatus statusCode = ex.getStatusCode();
79+
log.error("Failed to acquire access token. Status code: {}, Error Message: {}",
80+
statusCode, ex.getMessage());
81+
throw new RuntimeException("Error while requesting token:" + ex.getMessage(), ex);
82+
}
83+
}
84+
85+
public boolean isTokenExpired() {
86+
return this.bearerToken == null || Instant.now().isAfter(this.expireTime);
87+
}
88+
89+
/**
90+
* Refreshes the bearer token by retrieving a new one from CrowdStrike.
91+
*/
92+
public void refreshToken() {
93+
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.opensearch.dataprepper.plugins.source.crowdstrike.utils;
2+
3+
/**
4+
* The type Constants.
5+
*/
6+
public class Constants {
7+
8+
public static final String PLUGIN_NAME = "crowdstrike";
9+
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.opensearch.dataprepper.plugins.source.crowdstrike;
2+
3+
import org.junit.jupiter.api.extension.ExtendWith;
4+
import org.mockito.junit.jupiter.MockitoExtension;
5+
6+
@ExtendWith(MockitoExtension.class)
7+
public class CrowdStrikeClientTest {
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.opensearch.dataprepper.plugins.source.crowdstrike;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import org.junit.jupiter.api.BeforeEach;
5+
import org.junit.jupiter.api.Test;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.assertFalse;
11+
import static org.junit.jupiter.api.Assertions.assertNotNull;
12+
import static org.junit.jupiter.api.Assertions.assertTrue;
13+
14+
15+
public class CrowdStrikeSourceConfigTest {
16+
17+
private static final ObjectMapper objectMapper = new ObjectMapper();
18+
private CrowdStrikeSourceConfig config;
19+
20+
private static final int DEFAULT_WORKERS = 5;
21+
22+
@BeforeEach
23+
void setup() {
24+
config = new CrowdStrikeSourceConfig();
25+
}
26+
27+
@Test
28+
void testDefaultValues() {
29+
assertEquals(DEFAULT_WORKERS, config.getNumWorkers());
30+
assertFalse(config.isAcknowledgments());
31+
}
32+
33+
@Test
34+
void testDeserializationWithValues() throws Exception {
35+
Map<String, Object> yamlConfig = new HashMap<>();
36+
Map<String, Object> authMap = new HashMap<>();
37+
authMap.put("client_id", "dummy-client-id");
38+
authMap.put("client_secret", "dummy-client-secret");
39+
yamlConfig.put("authentication", authMap);
40+
yamlConfig.put("acknowledgments", true);
41+
yamlConfig.put("workers", 10);
42+
43+
String json = objectMapper.writeValueAsString(yamlConfig);
44+
CrowdStrikeSourceConfig loadedConfig = objectMapper.readValue(json, CrowdStrikeSourceConfig.class);
45+
46+
assertNotNull(loadedConfig.getAuthenticationConfig());
47+
assertTrue(loadedConfig.isAcknowledgments());
48+
assertEquals(10, loadedConfig.getNumWorkers());
49+
}
50+
51+
}

0 commit comments

Comments
 (0)