Skip to content

Commit 2e02597

Browse files
committed
Merge remote-tracking branch 'origin/release/v11' into release/v11
2 parents ffb080c + df67d52 commit 2e02597

File tree

16 files changed

+1871
-82
lines changed

16 files changed

+1871
-82
lines changed

plugins/crowdStrike/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# UTMStack Plugin for CrowdStrike Falcon
2+
3+
## Description
4+
5+
UTMStack Plugin for CrowdStrike Falcon is a connector developed in Golang that receives real-time events from `CrowdStrike Falcon Event Streams` and sends them to the `UTMStack` processing server for further processing.
6+
7+
This connector uses a `GRPC` client to communicate with the UTMStack processing server. The client connects through a `Unix socket` that is created in the UTMStack working directory.
8+
9+
To obtain the events, `CrowdStrike GoFalcon SDK` is used to communicate with the Falcon Event Streams API, providing real-time security event data from your CrowdStrike environment.
10+
11+
Please note that the connector requires valid CrowdStrike Falcon API credentials to run. The connector will not work without proper authentication.
12+
13+
## Configuration
14+
15+
The plugin requires the following configuration parameters:
16+
17+
- **client_id**: OAuth2 Client ID for CrowdStrike Falcon API
18+
- **client_secret**: OAuth2 Client Secret for CrowdStrike Falcon API
19+
- **member_cid**: (Optional) Member CID for MSSP environments
20+
- **cloud**: Falcon cloud region (us-1, us-2, eu-1, us-gov-1)
21+
22+
## Features
23+
24+
- Real-time event streaming from CrowdStrike Falcon
25+
- Automatic stream discovery and processing
26+
- Error handling and retry mechanisms
27+
- Event batching to optimize performance
28+
- Timeout controls to prevent blocking
29+
- Structured JSON event formatting
30+
31+
## Authentication
32+
33+
This plugin uses OAuth2 authentication with CrowdStrike Falcon API. You need to:
34+
35+
1. Create API client credentials in your CrowdStrike console
36+
2. Ensure the client has the required scopes for Event Streams API
37+
3. Configure the credentials in UTMStack module configuration
38+
39+
For more information on creating API credentials, visit: https://falcon.crowdstrike.com/support/api-clients-and-keys

plugins/crowdStrike/check.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"strings"
7+
"time"
8+
9+
"github.com/threatwinds/go-sdk/catcher"
10+
)
11+
12+
func connectionChecker(url string) error {
13+
checkConn := func() error {
14+
if err := checkConnection(url); err != nil {
15+
return fmt.Errorf("connection failed: %v", err)
16+
}
17+
return nil
18+
}
19+
20+
if err := infiniteRetryIfXError(checkConn, "connection failed"); err != nil {
21+
return err
22+
}
23+
24+
return nil
25+
}
26+
27+
func checkConnection(url string) error {
28+
client := &http.Client{
29+
Timeout: 30 * time.Second,
30+
}
31+
32+
req, err := http.NewRequest(http.MethodGet, url, nil)
33+
if err != nil {
34+
return err
35+
}
36+
37+
resp, err := client.Do(req)
38+
if err != nil {
39+
return err
40+
}
41+
defer func() {
42+
err := resp.Body.Close()
43+
if err != nil {
44+
_ = catcher.Error("error closing response body: %v", err, nil)
45+
}
46+
}()
47+
48+
return nil
49+
}
50+
51+
func infiniteRetryIfXError(f func() error, exception string) error {
52+
var xErrorWasLogged bool
53+
54+
for {
55+
err := f()
56+
if err != nil && is(err, exception) {
57+
if !xErrorWasLogged {
58+
_ = catcher.Error("An error occurred (%s), will keep retrying indefinitely...", err, nil)
59+
xErrorWasLogged = true
60+
}
61+
time.Sleep(wait)
62+
continue
63+
}
64+
65+
return err
66+
}
67+
}
68+
69+
func is(e error, args ...string) bool {
70+
for _, arg := range args {
71+
if strings.Contains(e.Error(), arg) {
72+
return true
73+
}
74+
}
75+
return false
76+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"strings"
8+
sync "sync"
9+
"time"
10+
11+
"github.com/threatwinds/go-sdk/catcher"
12+
"github.com/threatwinds/go-sdk/plugins"
13+
"google.golang.org/grpc"
14+
codes "google.golang.org/grpc/codes"
15+
"google.golang.org/grpc/connectivity"
16+
"google.golang.org/grpc/credentials/insecure"
17+
"google.golang.org/grpc/metadata"
18+
"google.golang.org/grpc/status"
19+
)
20+
21+
const (
22+
reconnectDelay = 5 * time.Second
23+
maxMessageSize = 1024 * 1024 * 1024
24+
)
25+
26+
var (
27+
cnf *ConfigurationSection
28+
mu sync.Mutex
29+
30+
internalKey string
31+
modulesConfigHost string
32+
)
33+
34+
func GetConfig() *ConfigurationSection {
35+
mu.Lock()
36+
defer mu.Unlock()
37+
if cnf == nil {
38+
return &ConfigurationSection{}
39+
}
40+
return cnf
41+
}
42+
43+
func StartConfigurationSystem() {
44+
for {
45+
pluginConfig := plugins.PluginCfg("com.utmstack", false)
46+
if !pluginConfig.Exists() {
47+
_ = catcher.Error("plugin configuration not found", nil, nil)
48+
time.Sleep(reconnectDelay)
49+
continue
50+
}
51+
internalKey = pluginConfig.Get("internalKey").String()
52+
modulesConfigHost = pluginConfig.Get("modulesConfig").String()
53+
54+
if internalKey == "" || modulesConfigHost == "" {
55+
fmt.Println("Internal key or Modules Config Host is not set, skipping UTMStack plugin execution")
56+
time.Sleep(reconnectDelay)
57+
continue
58+
}
59+
break
60+
}
61+
62+
for {
63+
ctx, cancel := context.WithCancel(context.Background())
64+
defer cancel()
65+
ctx = metadata.AppendToOutgoingContext(ctx, "internal-key", internalKey)
66+
conn, err := grpc.NewClient(
67+
modulesConfigHost,
68+
grpc.WithTransportCredentials(insecure.NewCredentials()),
69+
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMessageSize)),
70+
)
71+
72+
if err != nil {
73+
catcher.Error("Failed to connect to server", err, nil)
74+
cancel()
75+
time.Sleep(reconnectDelay)
76+
continue
77+
}
78+
79+
state := conn.GetState()
80+
if state == connectivity.Shutdown || state == connectivity.TransientFailure {
81+
catcher.Error("Connection is in shutdown or transient failure state", nil, nil)
82+
cancel()
83+
time.Sleep(reconnectDelay)
84+
continue
85+
}
86+
87+
client := NewConfigServiceClient(conn)
88+
stream, err := client.StreamConfig(ctx)
89+
if err != nil {
90+
catcher.Error("Failed to create stream", err, nil)
91+
conn.Close()
92+
cancel()
93+
time.Sleep(reconnectDelay)
94+
continue
95+
}
96+
97+
err = stream.Send(&BiDirectionalMessage{
98+
Payload: &BiDirectionalMessage_PluginInit{
99+
PluginInit: &PluginInit{Type: PluginType_CROWDSTRIKE},
100+
},
101+
})
102+
if err != nil {
103+
catcher.Error("Failed to send PluginInit", err, nil)
104+
conn.Close()
105+
cancel()
106+
time.Sleep(reconnectDelay)
107+
continue
108+
}
109+
110+
for {
111+
in, err := stream.Recv()
112+
if err != nil {
113+
if strings.Contains(err.Error(), "EOF") {
114+
catcher.Info("Stream closed by server, reconnecting...", nil)
115+
conn.Close()
116+
cancel()
117+
time.Sleep(reconnectDelay)
118+
break
119+
}
120+
st, ok := status.FromError(err)
121+
if ok && (st.Code() == codes.Unavailable || st.Code() == codes.Canceled) {
122+
catcher.Error("Stream error: "+st.Message(), err, nil)
123+
conn.Close()
124+
cancel()
125+
time.Sleep(reconnectDelay)
126+
break
127+
} else {
128+
catcher.Error("Stream receive error", err, nil)
129+
time.Sleep(reconnectDelay)
130+
continue
131+
}
132+
}
133+
134+
switch message := in.Payload.(type) {
135+
case *BiDirectionalMessage_Config:
136+
log.Printf("Received configuration update: %v", message.Config)
137+
cnf = message.Config
138+
}
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)