Skip to content

Commit 7c77a86

Browse files
committed
feat(plugins): implement ThreadWinds ingestion plugin
Add comprehensive plugin for extracting security entities from UTMStack incidents/alerts and ingesting them into ThreadWinds threat intelligence platform. Core Features: - Extract 100+ entity types (IPs, domains, hashes, emails, URLs, files, processes, CVEs, malware indicators, certificates, etc.) - Build 100+ bidirectional entity associations (IP↔Domain, File↔Hash, Process↔File, CVE↔CPE, etc.) - Automatic ThreadWinds API registration using instance credentials - OpenSearch integration for alert/event retrieval - 5-minute polling scheduler for active incidents (OPEN/IN_REVIEW) - Graceful shutdown and comprehensive error handling
1 parent 971e3fc commit 7c77a86

32 files changed

+3375
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# UTMStack Plugin for ThreadWinds Ingestion
2+
3+
4+
## Description
5+
6+
UTMStack Plugin for ThreadWinds Ingestion is a connector developed in Golang that extracts security entities from `UTMStack incidents and alerts` and sends them to the `ThreadWinds` threat intelligence platform.
7+
8+
This plugin processes incidents from UTMStack, extracts entities (IPs, domains, hashes, emails, etc.) from their associated alerts and events, and ingests them into ThreadWinds for global threat intelligence correlation and enrichment.
9+
10+
The connector automatically registers with ThreadWinds services using the admin email from the UTMStack system. It periodically polls for recent incidents, extracts all relevant entities (network indicators, file hashes, user identities, etc.), builds associations between entities, and sends them to ThreadWinds for analysis.
11+
12+
### Requirements
13+
**ThreadWinds Credentials:**
14+
15+
- API Key
16+
- API Secret
17+
18+
Please note that the connector automatically registers with ThreadWinds using the admin email if credentials are not already configured. The connector requires a valid admin email to run.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package config
2+
3+
type TWConfig struct {
4+
InternalKey string
5+
BackendURL string
6+
ThreadWindsURL string
7+
OpenSearchHost string
8+
OpenSearchPort string
9+
DBHost string
10+
DBPort string
11+
DBUser string
12+
DBPassword string
13+
DBName string
14+
}
15+
16+
func GetTWConfig() (*TWConfig, error) {
17+
cfg := &TWConfig{
18+
InternalKey: GetInternalKey(),
19+
BackendURL: GetBackendUrl(),
20+
ThreadWindsURL: GetThreadWindsURL(),
21+
OpenSearchHost: GetOpenSearchHost(),
22+
OpenSearchPort: GetOpenSearchPort(),
23+
DBHost: GetDBHost(),
24+
DBPort: GetDBPort(),
25+
DBUser: GetDBUser(),
26+
DBPassword: GetDBPassword(),
27+
DBName: GetDBName(),
28+
}
29+
30+
return cfg, nil
31+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package config
2+
3+
import (
4+
"os"
5+
"strings"
6+
7+
"github.com/utmstack/UTMStack/threadwinds-ingestion/utils"
8+
)
9+
10+
func GetInternalKey() string {
11+
return utils.Getenv("INTERNAL_KEY")
12+
}
13+
14+
func GetBackendUrl() string {
15+
return utils.Getenv("BACKEND_URL")
16+
}
17+
18+
func GetThreadWindsURL() string {
19+
if isDevEnvironment() {
20+
return "https://apis.dev.threatwinds.com"
21+
}
22+
return "https://apis.threatwinds.com"
23+
}
24+
25+
func GetOpenSearchHost() string {
26+
return utils.Getenv("OPENSEARCH_HOST")
27+
}
28+
29+
func GetOpenSearchPort() string {
30+
return utils.Getenv("OPENSEARCH_PORT")
31+
}
32+
33+
func GetDBHost() string {
34+
return utils.Getenv("DB_HOST")
35+
}
36+
37+
func GetDBPort() string {
38+
return utils.Getenv("DB_PORT")
39+
}
40+
41+
func GetDBUser() string {
42+
return utils.Getenv("DB_USER")
43+
}
44+
45+
func GetDBPassword() string {
46+
return utils.Getenv("DB_PASS")
47+
}
48+
49+
func GetDBName() string {
50+
return utils.Getenv("DB_NAME")
51+
}
52+
53+
func isDevEnvironment() bool {
54+
env := os.Getenv("ENV")
55+
if env != "" {
56+
if strings.Contains(env, "-dev") ||
57+
strings.Contains(env, "-qa") ||
58+
strings.Contains(env, "-rc") {
59+
return true
60+
}
61+
}
62+
63+
return false
64+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
module github.com/utmstack/UTMStack/threadwinds-ingestion
2+
3+
go 1.25.5
4+
5+
require (
6+
github.com/AtlasInsideCorp/AtlasInsideAES v1.0.0
7+
github.com/opensearch-project/opensearch-go/v2 v2.3.0
8+
github.com/threatwinds/go-sdk v1.0.51
9+
golang.org/x/sync v0.19.0
10+
gopkg.in/yaml.v2 v2.4.0
11+
)
12+
13+
require (
14+
cel.dev/expr v0.25.1 // indirect
15+
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
16+
github.com/bytedance/gopkg v0.1.3 // indirect
17+
github.com/bytedance/sonic v1.14.2 // indirect
18+
github.com/bytedance/sonic/loader v0.4.0 // indirect
19+
github.com/cloudwego/base64x v0.1.6 // indirect
20+
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
21+
github.com/gin-contrib/sse v1.1.0 // indirect
22+
github.com/gin-gonic/gin v1.11.0 // indirect
23+
github.com/go-playground/locales v0.14.1 // indirect
24+
github.com/go-playground/universal-translator v0.18.1 // indirect
25+
github.com/go-playground/validator/v10 v10.30.1 // indirect
26+
github.com/goccy/go-json v0.10.5 // indirect
27+
github.com/goccy/go-yaml v1.19.2 // indirect
28+
github.com/google/cel-go v0.26.1 // indirect
29+
github.com/google/uuid v1.6.0 // indirect
30+
github.com/json-iterator/go v1.1.12 // indirect
31+
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
32+
github.com/leodido/go-urn v1.4.0 // indirect
33+
github.com/mattn/go-isatty v0.0.20 // indirect
34+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
35+
github.com/modern-go/reflect2 v1.0.2 // indirect
36+
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
37+
github.com/quic-go/qpack v0.6.0 // indirect
38+
github.com/quic-go/quic-go v0.59.0 // indirect
39+
github.com/stoewer/go-strcase v1.3.1 // indirect
40+
github.com/tidwall/gjson v1.18.0 // indirect
41+
github.com/tidwall/match v1.2.0 // indirect
42+
github.com/tidwall/pretty v1.2.1 // indirect
43+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
44+
github.com/ugorji/go/codec v1.3.1 // indirect
45+
go.yaml.in/yaml/v2 v2.4.3 // indirect
46+
golang.org/x/arch v0.23.0 // indirect
47+
golang.org/x/crypto v0.47.0 // indirect
48+
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
49+
golang.org/x/net v0.49.0 // indirect
50+
golang.org/x/sys v0.40.0 // indirect
51+
golang.org/x/text v0.33.0 // indirect
52+
google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3 // indirect
53+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect
54+
google.golang.org/grpc v1.78.0 // indirect
55+
google.golang.org/protobuf v1.36.11 // indirect
56+
gopkg.in/yaml.v3 v3.0.1 // indirect
57+
sigs.k8s.io/yaml v1.6.0 // indirect
58+
)

0 commit comments

Comments
 (0)