1+ package detection_rule
2+
3+ import (
4+ "context"
5+ "encoding/json"
6+ "fmt"
7+ "net/url"
8+
9+ "github.com/elastic/terraform-provider-elasticstack/internal/clients"
10+ "github.com/hashicorp/terraform-plugin-framework/diag"
11+ )
12+
13+ // SecurityDetectionRuleRequest represents a security detection rule creation/update request
14+ type SecurityDetectionRuleRequest struct {
15+ Name string `json:"name"`
16+ Description string `json:"description"`
17+ Type string `json:"type"`
18+ Query * string `json:"query,omitempty"`
19+ Language * string `json:"language,omitempty"`
20+ Index []string `json:"index,omitempty"`
21+ Severity string `json:"severity"`
22+ Risk int `json:"risk_score"`
23+ Enabled bool `json:"enabled"`
24+ Tags []string `json:"tags,omitempty"`
25+ From string `json:"from"`
26+ To string `json:"to"`
27+ Interval string `json:"interval"`
28+ Meta * map [string ]any `json:"meta,omitempty"`
29+ Author []string `json:"author,omitempty"`
30+ License * string `json:"license,omitempty"`
31+ RuleNameOverride * string `json:"rule_name_override,omitempty"`
32+ TimestampOverride * string `json:"timestamp_override,omitempty"`
33+ Note * string `json:"note,omitempty"`
34+ References []string `json:"references,omitempty"`
35+ FalsePositives []string `json:"false_positives,omitempty"`
36+ ExceptionsList []any `json:"exceptions_list,omitempty"`
37+ Version int `json:"version"`
38+ MaxSignals int `json:"max_signals"`
39+ }
40+
41+ // SecurityDetectionRuleResponse represents the API response for a security detection rule
42+ type SecurityDetectionRuleResponse struct {
43+ ID string `json:"id"`
44+ Name string `json:"name"`
45+ Description string `json:"description"`
46+ Type string `json:"type"`
47+ Query * string `json:"query,omitempty"`
48+ Language * string `json:"language,omitempty"`
49+ Index []string `json:"index,omitempty"`
50+ Severity string `json:"severity"`
51+ Risk int `json:"risk_score"`
52+ Enabled bool `json:"enabled"`
53+ Tags []string `json:"tags,omitempty"`
54+ From string `json:"from"`
55+ To string `json:"to"`
56+ Interval string `json:"interval"`
57+ Meta * map [string ]any `json:"meta,omitempty"`
58+ Author []string `json:"author,omitempty"`
59+ License * string `json:"license,omitempty"`
60+ RuleNameOverride * string `json:"rule_name_override,omitempty"`
61+ TimestampOverride * string `json:"timestamp_override,omitempty"`
62+ Note * string `json:"note,omitempty"`
63+ References []string `json:"references,omitempty"`
64+ FalsePositives []string `json:"false_positives,omitempty"`
65+ ExceptionsList []any `json:"exceptions_list,omitempty"`
66+ Version int `json:"version"`
67+ MaxSignals int `json:"max_signals"`
68+ CreatedAt string `json:"created_at"`
69+ CreatedBy string `json:"created_by"`
70+ UpdatedAt string `json:"updated_at"`
71+ UpdatedBy string `json:"updated_by"`
72+ }
73+
74+ // CreateSecurityDetectionRule creates a new security detection rule
75+ func CreateSecurityDetectionRule (ctx context.Context , client * clients.ApiClient , spaceId string , ruleId * string , rule * SecurityDetectionRuleRequest ) (* SecurityDetectionRuleResponse , diag.Diagnostics ) {
76+ var diags diag.Diagnostics
77+
78+ kbClient , err := client .GetKibanaClient ()
79+ if err != nil {
80+ diags .AddError ("Failed to get Kibana client" , err .Error ())
81+ return nil , diags
82+ }
83+
84+ // Create the URL path
85+ path := fmt .Sprintf ("/s/%s/api/detection_engine/rules" , url .PathEscape (spaceId ))
86+
87+ // Execute the request using resty
88+ resp , err := kbClient .Client .R ().SetBody (rule ).Post (path )
89+ if err != nil {
90+ diags .AddError ("Failed to execute request" , err .Error ())
91+ return nil , diags
92+ }
93+
94+ // Handle non-2xx status codes
95+ if resp .StatusCode () >= 300 {
96+ diags .AddError (
97+ "API request failed" ,
98+ fmt .Sprintf ("Status: %d, URL: %s, Body: %s" , resp .StatusCode (), resp .Request .URL , string (resp .Body ())),
99+ )
100+ return nil , diags
101+ }
102+
103+ // Parse the response
104+ var result SecurityDetectionRuleResponse
105+ if err := json .Unmarshal (resp .Body (), & result ); err != nil {
106+ diags .AddError ("Failed to decode response" , err .Error ())
107+ return nil , diags
108+ }
109+
110+ return & result , diags
111+ }
112+
113+ // GetSecurityDetectionRule retrieves a security detection rule by ID
114+ func GetSecurityDetectionRule (ctx context.Context , client * clients.ApiClient , spaceId , ruleId string ) (* SecurityDetectionRuleResponse , diag.Diagnostics ) {
115+ var diags diag.Diagnostics
116+
117+ kbClient , err := client .GetKibanaClient ()
118+ if err != nil {
119+ diags .AddError ("Failed to get Kibana client" , err .Error ())
120+ return nil , diags
121+ }
122+
123+ // Create the URL path
124+ path := fmt .Sprintf ("/s/%s/api/detection_engine/rules?id=%s" , url .PathEscape (spaceId ), url .QueryEscape (ruleId ))
125+
126+ // Execute the request using resty
127+ resp , err := kbClient .Client .R ().Get (path )
128+ if err != nil {
129+ diags .AddError ("Failed to execute request" , err .Error ())
130+ return nil , diags
131+ }
132+
133+ // Handle not found
134+ if resp .StatusCode () == 404 {
135+ return nil , diags // Rule not found
136+ }
137+
138+ // Handle other non-2xx status codes
139+ if resp .StatusCode () >= 300 {
140+ diags .AddError (
141+ "API request failed" ,
142+ fmt .Sprintf ("Status: %d, URL: %s, Body: %s" , resp .StatusCode (), resp .Request .URL , string (resp .Body ())),
143+ )
144+ return nil , diags
145+ }
146+
147+ // Parse the response
148+ var result SecurityDetectionRuleResponse
149+ if err := json .Unmarshal (resp .Body (), & result ); err != nil {
150+ diags .AddError ("Failed to decode response" , err .Error ())
151+ return nil , diags
152+ }
153+
154+ return & result , diags
155+ }
156+
157+ // UpdateSecurityDetectionRule updates an existing security detection rule
158+ func UpdateSecurityDetectionRule (ctx context.Context , client * clients.ApiClient , spaceId , ruleId string , rule * SecurityDetectionRuleRequest ) (* SecurityDetectionRuleResponse , diag.Diagnostics ) {
159+ var diags diag.Diagnostics
160+
161+ kbClient , err := client .GetKibanaClient ()
162+ if err != nil {
163+ diags .AddError ("Failed to get Kibana client" , err .Error ())
164+ return nil , diags
165+ }
166+
167+ // Create the URL path
168+ path := fmt .Sprintf ("/s/%s/api/detection_engine/rules" , url .PathEscape (spaceId ))
169+
170+ // Execute the request using resty
171+ resp , err := kbClient .Client .R ().SetBody (rule ).Put (path )
172+ if err != nil {
173+ diags .AddError ("Failed to execute request" , err .Error ())
174+ return nil , diags
175+ }
176+
177+ // Handle non-2xx status codes
178+ if resp .StatusCode () >= 300 {
179+ diags .AddError (
180+ "API request failed" ,
181+ fmt .Sprintf ("Status: %d, URL: %s, Body: %s" , resp .StatusCode (), resp .Request .URL , string (resp .Body ())),
182+ )
183+ return nil , diags
184+ }
185+
186+ // Parse the response
187+ var result SecurityDetectionRuleResponse
188+ if err := json .Unmarshal (resp .Body (), & result ); err != nil {
189+ diags .AddError ("Failed to decode response" , err .Error ())
190+ return nil , diags
191+ }
192+
193+ return & result , diags
194+ }
195+
196+ // DeleteSecurityDetectionRule deletes a security detection rule by ID
197+ func DeleteSecurityDetectionRule (ctx context.Context , client * clients.ApiClient , spaceId , ruleId string ) diag.Diagnostics {
198+ var diags diag.Diagnostics
199+
200+ kbClient , err := client .GetKibanaClient ()
201+ if err != nil {
202+ diags .AddError ("Failed to get Kibana client" , err .Error ())
203+ return diags
204+ }
205+
206+ // Create the URL path
207+ path := fmt .Sprintf ("/s/%s/api/detection_engine/rules?id=%s" , url .PathEscape (spaceId ), url .QueryEscape (ruleId ))
208+
209+ // Execute the request using resty
210+ resp , err := kbClient .Client .R ().Delete (path )
211+ if err != nil {
212+ diags .AddError ("Failed to execute request" , err .Error ())
213+ return diags
214+ }
215+
216+ // Handle not found (rule might already be deleted)
217+ if resp .StatusCode () == 404 {
218+ return diags // Already deleted, no error
219+ }
220+
221+ // Handle other non-2xx status codes
222+ if resp .StatusCode () >= 300 {
223+ diags .AddError (
224+ "API request failed" ,
225+ fmt .Sprintf ("Status: %d, URL: %s, Body: %s" , resp .StatusCode (), resp .Request .URL , string (resp .Body ())),
226+ )
227+ return diags
228+ }
229+
230+ return diags
231+ }
0 commit comments