Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [Unreleased]

- Create `elasticstack_kibana_security_detection_rule` resource to manage Kibana Security Detection Rules. ([#1290](https://github.com/elastic/terraform-provider-elasticstack/pull/1290))
- Create `elasticstack_kibana_maintenance_window` resource. ([#1224](https://github.com/elastic/terraform-provider-elasticstack/pull/1224))
- Add support for `solution` field in `elasticstack_kibana_space` resource and data source ([#1102](https://github.com/elastic/terraform-provider-elasticstack/issues/1102))
- Add `slo_id` validation to `elasticstack_kibana_slo` ([#1221](https://github.com/elastic/terraform-provider-elasticstack/pull/1221))
Expand Down
65 changes: 65 additions & 0 deletions docs/resources/kibana_security_detection_rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "elasticstack_kibana_security_detection_rule Resource - terraform-provider-elasticstack"
subcategory: ""
description: |-
Creates or updates a Kibana security detection rule. See https://www.elastic.co/guide/en/security/current/rules-api-create.html
---

# elasticstack_kibana_security_detection_rule (Resource)

Creates or updates a Kibana security detection rule. See https://www.elastic.co/guide/en/security/current/rules-api-create.html


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a terraform usage example


<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `description` (String) The description of the detection rule.
- `name` (String) The name of the detection rule.
- `severity` (String) The severity of the rule. Valid values are: low, medium, high, critical.
- `type` (String) The rule type. Valid values are: eql, query, machine_learning, threshold, threat_match, new_terms.

### Optional

- `author` (List of String) String array containing the rule's author(s).
- `enabled` (Boolean) Determines whether the rule is enabled.
- `exceptions_list` (List of String) List of exceptions that prevent alerts from being generated.
- `false_positives` (List of String) String array describing common reasons why the rule may issue false-positive alerts.
- `from` (String) Time from which data is analyzed each time the rule executes, using date math syntax.
- `index` (List of String) A list of index patterns to search.
- `interval` (String) How often the rule executes.
- `kibana_connection` (Block List) Kibana connection configuration block. (see [below for nested schema](#nestedblock--kibana_connection))
- `language` (String) The query language. Valid values are: kuery, lucene, eql.
- `license` (String) The rule's license.
- `max_signals` (Number) Maximum number of alerts the rule can produce during a single execution.
- `meta` (String) Optional metadata about the rule as a JSON string.
- `note` (String) Notes to help investigate alerts produced by the rule.
- `query` (String) The query that the rule will use to generate alerts.
- `references` (List of String) String array containing notes about or references to relevant information about the rule.
- `risk` (Number) A numerical representation of the alert's severity from 1-100.
- `rule_id` (String) The identifier for the rule. If not provided, an ID is randomly generated.
- `rule_name_override` (String) Sets the source field for the alert's rule name.
- `space_id` (String) An identifier for the space. If space_id is not provided, the default space is used.
- `tags` (List of String) String array containing words and phrases to help categorize, filter, and search rules.
- `timestamp_override` (String) Sets the time field used to query indices.
- `to` (String) Time to which data is analyzed each time the rule executes, using date math syntax.
- `version` (Number) The rule's version number.

### Read-Only

- `id` (String) Internal identifier of the resource

<a id="nestedblock--kibana_connection"></a>
### Nested Schema for `kibana_connection`

Optional:

- `api_key` (String, Sensitive) API Key to use for authentication to Kibana
- `ca_certs` (List of String) A list of paths to CA certificates to validate the certificate presented by the Kibana server.
- `endpoints` (List of String, Sensitive) A comma-separated list of endpoints where the terraform provider will point to, this must include the http(s) schema and port number.
- `insecure` (Boolean) Disable TLS certificate validation
- `password` (String, Sensitive) Password to use for API authentication to Kibana.
- `username` (String) Username to use for API authentication to Kibana.
48 changes: 48 additions & 0 deletions internal/kibana/security/detection_rule/acc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package detection_rule_test

import (
"testing"

"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestAccResourceKibanaSecurityDetectionRule(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccResourceKibanaSecurityDetectionRuleCreate(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_kibana_security_detection_rule.test", "name", "Test Detection Rule"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_detection_rule.test", "description", "Test security detection rule"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_detection_rule.test", "type", "query"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_detection_rule.test", "severity", "medium"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_detection_rule.test", "enabled", "true"),
),
},
},
})
}

func testAccResourceKibanaSecurityDetectionRuleCreate() string {
return `
provider "elasticstack" {
kibana {}
}

resource "elasticstack_kibana_security_detection_rule" "test" {
name = "Test Detection Rule"
description = "Test security detection rule"
type = "query"
query = "*:*"
language = "kuery"
severity = "medium"
enabled = true
tags = ["test"]
interval = "5m"
from = "now-6m"
to = "now"
}`
}
231 changes: 231 additions & 0 deletions internal/kibana/security/detection_rule/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package detection_rule

import (
"context"
"encoding/json"
"fmt"
"net/url"

"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/hashicorp/terraform-plugin-framework/diag"
)

// SecurityDetectionRuleRequest represents a security detection rule creation/update request
type SecurityDetectionRuleRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
Query *string `json:"query,omitempty"`
Language *string `json:"language,omitempty"`
Index []string `json:"index,omitempty"`
Severity string `json:"severity"`
Risk int `json:"risk_score"`
Enabled bool `json:"enabled"`

Check failure on line 23 in internal/kibana/security/detection_rule/client.go

View workflow job for this annotation

GitHub Actions / copilot

assignment mismatch: 1 variable but kbapi.NewClientWithResponses returns 2 values
Tags []string `json:"tags,omitempty"`
From string `json:"from"`
To string `json:"to"`
Interval string `json:"interval"`
Meta *map[string]any `json:"meta,omitempty"`
Author []string `json:"author,omitempty"`
License *string `json:"license,omitempty"`
RuleNameOverride *string `json:"rule_name_override,omitempty"`
TimestampOverride *string `json:"timestamp_override,omitempty"`
Note *string `json:"note,omitempty"`
References []string `json:"references,omitempty"`
FalsePositives []string `json:"false_positives,omitempty"`

Check failure on line 35 in internal/kibana/security/detection_rule/client.go

View workflow job for this annotation

GitHub Actions / copilot

cannot use rule.Enabled (variable of type bool) as *"github.com/elastic/terraform-provider-elasticstack/generated/kbapi".SecurityDetectionsAPIIsRuleEnabled value in struct literal
ExceptionsList []any `json:"exceptions_list,omitempty"`

Check failure on line 36 in internal/kibana/security/detection_rule/client.go

View workflow job for this annotation

GitHub Actions / copilot

cannot use rule.From (variable of type string) as *"github.com/elastic/terraform-provider-elasticstack/generated/kbapi".SecurityDetectionsAPIRuleIntervalFrom value in struct literal
Version int `json:"version"`

Check failure on line 37 in internal/kibana/security/detection_rule/client.go

View workflow job for this annotation

GitHub Actions / copilot

cannot use rule.To (variable of type string) as *"github.com/elastic/terraform-provider-elasticstack/generated/kbapi".SecurityDetectionsAPIRuleIntervalTo value in struct literal
MaxSignals int `json:"max_signals"`

Check failure on line 38 in internal/kibana/security/detection_rule/client.go

View workflow job for this annotation

GitHub Actions / copilot

cannot use rule.Interval (variable of type string) as *"github.com/elastic/terraform-provider-elasticstack/generated/kbapi".SecurityDetectionsAPIRuleInterval value in struct literal
}

// SecurityDetectionRuleResponse represents the API response for a security detection rule
type SecurityDetectionRuleResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
Query *string `json:"query,omitempty"`
Language *string `json:"language,omitempty"`

Check failure on line 48 in internal/kibana/security/detection_rule/client.go

View workflow job for this annotation

GitHub Actions / copilot

cannot use kbapi.SecurityDetectionsAPIKqlQueryLanguage(*rule.Language) (value of string type "github.com/elastic/terraform-provider-elasticstack/generated/kbapi".SecurityDetectionsAPIKqlQueryLanguage) as *"github.com/elastic/terraform-provider-elasticstack/generated/kbapi".SecurityDetectionsAPIKqlQueryLanguage value in assignment
Index []string `json:"index,omitempty"`
Severity string `json:"severity"`
Risk int `json:"risk_score"`
Enabled bool `json:"enabled"`
Tags []string `json:"tags,omitempty"`
From string `json:"from"`
To string `json:"to"`
Interval string `json:"interval"`
Meta *map[string]any `json:"meta,omitempty"`
Author []string `json:"author,omitempty"`
License *string `json:"license,omitempty"`
RuleNameOverride *string `json:"rule_name_override,omitempty"`
TimestampOverride *string `json:"timestamp_override,omitempty"`
Note *string `json:"note,omitempty"`
References []string `json:"references,omitempty"`
FalsePositives []string `json:"false_positives,omitempty"`
ExceptionsList []any `json:"exceptions_list,omitempty"`
Version int `json:"version"`
MaxSignals int `json:"max_signals"`
CreatedAt string `json:"created_at"`
CreatedBy string `json:"created_by"`
UpdatedAt string `json:"updated_at"`
UpdatedBy string `json:"updated_by"`
}

Check failure on line 72 in internal/kibana/security/detection_rule/client.go

View workflow job for this annotation

GitHub Actions / copilot

undefined: kbapi.SecurityDetectionsAPILicense

// CreateSecurityDetectionRule creates a new security detection rule
func CreateSecurityDetectionRule(ctx context.Context, client *clients.ApiClient, spaceId string, ruleId *string, rule *SecurityDetectionRuleRequest) (*SecurityDetectionRuleResponse, diag.Diagnostics) {
var diags diag.Diagnostics

kbClient, err := client.GetKibanaClient()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use GetKibanaOapiClient instead of GetKibanaClient

if err != nil {
diags.AddError("Failed to get Kibana client", err.Error())
return nil, diags
}

// Create the URL path
path := fmt.Sprintf("/s/%s/api/detection_engine/rules", url.PathEscape(spaceId))

// Execute the request using resty
resp, err := kbClient.Client.R().SetBody(rule).Post(path)
if err != nil {
diags.AddError("Failed to execute request", err.Error())
return nil, diags
}

// Handle non-2xx status codes
if resp.StatusCode() >= 300 {
diags.AddError(
"API request failed",
fmt.Sprintf("Status: %d, URL: %s, Body: %s", resp.StatusCode(), resp.Request.URL, string(resp.Body())),
)
return nil, diags
}

// Parse the response
var result SecurityDetectionRuleResponse
if err := json.Unmarshal(resp.Body(), &result); err != nil {
diags.AddError("Failed to decode response", err.Error())
return nil, diags
}

return &result, diags
}

// GetSecurityDetectionRule retrieves a security detection rule by ID
func GetSecurityDetectionRule(ctx context.Context, client *clients.ApiClient, spaceId, ruleId string) (*SecurityDetectionRuleResponse, diag.Diagnostics) {
var diags diag.Diagnostics

kbClient, err := client.GetKibanaClient()
if err != nil {
diags.AddError("Failed to get Kibana client", err.Error())
return nil, diags
}

// Create the URL path
path := fmt.Sprintf("/s/%s/api/detection_engine/rules?id=%s", url.PathEscape(spaceId), url.QueryEscape(ruleId))

// Execute the request using resty
resp, err := kbClient.Client.R().Get(path)
if err != nil {
diags.AddError("Failed to execute request", err.Error())
return nil, diags
}

// Handle not found
if resp.StatusCode() == 404 {
return nil, diags // Rule not found
}

// Handle other non-2xx status codes
if resp.StatusCode() >= 300 {
diags.AddError(
"API request failed",
fmt.Sprintf("Status: %d, URL: %s, Body: %s", resp.StatusCode(), resp.Request.URL, string(resp.Body())),
)
return nil, diags
}

// Parse the response
var result SecurityDetectionRuleResponse
if err := json.Unmarshal(resp.Body(), &result); err != nil {
diags.AddError("Failed to decode response", err.Error())
return nil, diags
}

return &result, diags
}

// UpdateSecurityDetectionRule updates an existing security detection rule
func UpdateSecurityDetectionRule(ctx context.Context, client *clients.ApiClient, spaceId, ruleId string, rule *SecurityDetectionRuleRequest) (*SecurityDetectionRuleResponse, diag.Diagnostics) {
var diags diag.Diagnostics

kbClient, err := client.GetKibanaClient()
if err != nil {
diags.AddError("Failed to get Kibana client", err.Error())
return nil, diags
}

// Create the URL path
path := fmt.Sprintf("/s/%s/api/detection_engine/rules", url.PathEscape(spaceId))

// Execute the request using resty
resp, err := kbClient.Client.R().SetBody(rule).Put(path)
if err != nil {
diags.AddError("Failed to execute request", err.Error())
return nil, diags
}

// Handle non-2xx status codes
if resp.StatusCode() >= 300 {
diags.AddError(
"API request failed",
fmt.Sprintf("Status: %d, URL: %s, Body: %s", resp.StatusCode(), resp.Request.URL, string(resp.Body())),
)
return nil, diags
}

// Parse the response
var result SecurityDetectionRuleResponse
if err := json.Unmarshal(resp.Body(), &result); err != nil {
diags.AddError("Failed to decode response", err.Error())
return nil, diags
}

return &result, diags
}

// DeleteSecurityDetectionRule deletes a security detection rule by ID
func DeleteSecurityDetectionRule(ctx context.Context, client *clients.ApiClient, spaceId, ruleId string) diag.Diagnostics {
var diags diag.Diagnostics

kbClient, err := client.GetKibanaClient()
if err != nil {
diags.AddError("Failed to get Kibana client", err.Error())
return diags
}

// Create the URL path
path := fmt.Sprintf("/s/%s/api/detection_engine/rules?id=%s", url.PathEscape(spaceId), url.QueryEscape(ruleId))

// Execute the request using resty
resp, err := kbClient.Client.R().Delete(path)
if err != nil {
diags.AddError("Failed to execute request", err.Error())
return diags
}

// Handle not found (rule might already be deleted)
if resp.StatusCode() == 404 {
return diags // Already deleted, no error
}

// Handle other non-2xx status codes
if resp.StatusCode() >= 300 {

Check failure on line 222 in internal/kibana/security/detection_rule/client.go

View workflow job for this annotation

GitHub Actions / copilot

assignment mismatch: 1 variable but kbapi.NewClientWithResponses returns 2 values
diags.AddError(
"API request failed",
fmt.Sprintf("Status: %d, URL: %s, Body: %s", resp.StatusCode(), resp.Request.URL, string(resp.Body())),
)

Check failure on line 226 in internal/kibana/security/detection_rule/client.go

View workflow job for this annotation

GitHub Actions / copilot

cannot convert &ruleId (value of type *string) to type *"github.com/elastic/terraform-provider-elasticstack/generated/kbapi".SecurityDetectionsAPIRuleObjectId
return diags
}

return diags
}
Loading
Loading