Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
resource "elasticstack_kibana_security_exception_list" "example" {
list_id = "my-detection-exception-list"
name = "My Detection Exception List"
description = "List of exceptions for security detection rules"
type = "detection"
namespace_type = "single"

tags = ["security", "detections"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
resource "elasticstack_kibana_security_exception_list" "endpoint" {
list_id = "my-endpoint-exception-list"
name = "My Endpoint Exception List"
description = "List of endpoint exceptions"
type = "endpoint"
namespace_type = "agnostic"

os_types = ["linux", "windows", "macos"]
tags = ["endpoint", "security"]
}
138 changes: 138 additions & 0 deletions internal/clients/kibana_oapi/exceptions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package kibana_oapi

import (
"context"
"net/http"

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

// GetExceptionList reads an exception list from the API by ID or list_id
func GetExceptionList(ctx context.Context, client *Client, spaceId string, params *kbapi.ReadExceptionListParams) (*kbapi.SecurityExceptionsAPIExceptionList, diag.Diagnostics) {
resp, err := client.API.ReadExceptionListWithResponse(ctx, kbapi.SpaceId(spaceId), params)
if err != nil {
return nil, diagutil.FrameworkDiagFromError(err)
}

switch resp.StatusCode() {
case http.StatusOK:
return resp.JSON200, nil
case http.StatusNotFound:
return nil, nil
default:
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
}
}

// CreateExceptionList creates a new exception list.
func CreateExceptionList(ctx context.Context, client *Client, spaceId string, body kbapi.CreateExceptionListJSONRequestBody) (*kbapi.SecurityExceptionsAPIExceptionList, diag.Diagnostics) {
resp, err := client.API.CreateExceptionListWithResponse(ctx, kbapi.SpaceId(spaceId), body)
if err != nil {
return nil, diagutil.FrameworkDiagFromError(err)
}

switch resp.StatusCode() {
case http.StatusOK:
return resp.JSON200, nil
default:
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
}
}

// UpdateExceptionList updates an existing exception list.
func UpdateExceptionList(ctx context.Context, client *Client, spaceId string, body kbapi.UpdateExceptionListJSONRequestBody) (*kbapi.SecurityExceptionsAPIExceptionList, diag.Diagnostics) {
resp, err := client.API.UpdateExceptionListWithResponse(ctx, kbapi.SpaceId(spaceId), body)
if err != nil {
return nil, diagutil.FrameworkDiagFromError(err)
}

switch resp.StatusCode() {
case http.StatusOK:
return resp.JSON200, nil
default:
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
}
}

// DeleteExceptionList deletes an existing exception list.
func DeleteExceptionList(ctx context.Context, client *Client, spaceId string, params *kbapi.DeleteExceptionListParams) diag.Diagnostics {
resp, err := client.API.DeleteExceptionListWithResponse(ctx, kbapi.SpaceId(spaceId), params)
if err != nil {
return diagutil.FrameworkDiagFromError(err)
}

switch resp.StatusCode() {
case http.StatusOK:
return nil
case http.StatusNotFound:
return nil
default:
return reportUnknownError(resp.StatusCode(), resp.Body)
}
}

// GetExceptionListItem reads an exception list item from the API by ID or item_id
func GetExceptionListItem(ctx context.Context, client *Client, spaceId string, params *kbapi.ReadExceptionListItemParams) (*kbapi.SecurityExceptionsAPIExceptionListItem, diag.Diagnostics) {
resp, err := client.API.ReadExceptionListItemWithResponse(ctx, kbapi.SpaceId(spaceId), params)
if err != nil {
return nil, diagutil.FrameworkDiagFromError(err)
}

switch resp.StatusCode() {
case http.StatusOK:
return resp.JSON200, nil
case http.StatusNotFound:
return nil, nil
default:
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
}
}

// CreateExceptionListItem creates a new exception list item.
func CreateExceptionListItem(ctx context.Context, client *Client, spaceId string, body kbapi.CreateExceptionListItemJSONRequestBody) (*kbapi.SecurityExceptionsAPIExceptionListItem, diag.Diagnostics) {
resp, err := client.API.CreateExceptionListItemWithResponse(ctx, kbapi.SpaceId(spaceId), body)
if err != nil {
return nil, diagutil.FrameworkDiagFromError(err)
}

switch resp.StatusCode() {
case http.StatusOK:
return resp.JSON200, nil
default:
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
}
}

// UpdateExceptionListItem updates an existing exception list item.
func UpdateExceptionListItem(ctx context.Context, client *Client, spaceId string, body kbapi.UpdateExceptionListItemJSONRequestBody) (*kbapi.SecurityExceptionsAPIExceptionListItem, diag.Diagnostics) {
resp, err := client.API.UpdateExceptionListItemWithResponse(ctx, kbapi.SpaceId(spaceId), body)
if err != nil {
return nil, diagutil.FrameworkDiagFromError(err)
}

switch resp.StatusCode() {
case http.StatusOK:
return resp.JSON200, nil
default:
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
}
}

// DeleteExceptionListItem deletes an existing exception list item.
func DeleteExceptionListItem(ctx context.Context, client *Client, spaceId string, params *kbapi.DeleteExceptionListItemParams) diag.Diagnostics {
resp, err := client.API.DeleteExceptionListItemWithResponse(ctx, kbapi.SpaceId(spaceId), params)
if err != nil {
return diagutil.FrameworkDiagFromError(err)
}

switch resp.StatusCode() {
case http.StatusOK:
return nil
case http.StatusNotFound:
return nil
default:
return reportUnknownError(resp.StatusCode(), resp.Body)
}
}
168 changes: 168 additions & 0 deletions internal/kibana/security_exception_list/acc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package security_exception_list_test

import (
"fmt"
"testing"

"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
"github.com/elastic/terraform-provider-elasticstack/internal/versionutils"
"github.com/google/uuid"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

var minExceptionListAPISupport = version.Must(version.NewVersion("7.9.0"))

func TestAccResourceExceptionList(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
Steps: []resource.TestStep{
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minExceptionListAPISupport),
ProtoV6ProviderFactories: acctest.Providers,
ConfigDirectory: acctest.NamedTestCaseDirectory("create"),
ConfigVariables: config.Variables{
"list_id": config.StringVariable("test-exception-list"),
"name": config.StringVariable("Test Exception List"),
"description": config.StringVariable("Test exception list for acceptance tests"),
"type": config.StringVariable("detection"),
"namespace_type": config.StringVariable("single"),
"tags": config.ListVariable(config.StringVariable("test")),
},
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_kibana_security_exception_list.test", "list_id", "test-exception-list"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_exception_list.test", "name", "Test Exception List"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_exception_list.test", "description", "Test exception list for acceptance tests"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_exception_list.test", "type", "detection"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_exception_list.test", "namespace_type", "single"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_exception_list.test", "tags.0", "test"),
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_exception_list.test", "id"),
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_exception_list.test", "created_at"),
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_exception_list.test", "created_by"),
),
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minExceptionListAPISupport),
ProtoV6ProviderFactories: acctest.Providers,
ConfigDirectory: acctest.NamedTestCaseDirectory("update"),
ConfigVariables: config.Variables{
"list_id": config.StringVariable("test-exception-list"),
"name": config.StringVariable("Test Exception List Updated"),
"description": config.StringVariable("Updated description"),
"type": config.StringVariable("detection"),
"namespace_type": config.StringVariable("single"),
"tags": config.ListVariable(config.StringVariable("test"), config.StringVariable("updated")),
},
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_kibana_security_exception_list.test", "name", "Test Exception List Updated"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_exception_list.test", "description", "Updated description"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_exception_list.test", "tags.0", "test"),
resource.TestCheckResourceAttr("elasticstack_kibana_security_exception_list.test", "tags.1", "updated"),
),
},
{ // Import
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minExceptionListAPISupport),
ProtoV6ProviderFactories: acctest.Providers,
ConfigDirectory: acctest.NamedTestCaseDirectory("update"),
ConfigVariables: config.Variables{
"list_id": config.StringVariable("test-exception-list"),
"name": config.StringVariable("Test Exception List Updated"),
"description": config.StringVariable("Updated description"),
"type": config.StringVariable("detection"),
"namespace_type": config.StringVariable("single"),
"tags": config.ListVariable(config.StringVariable("test"), config.StringVariable("updated")),
},
ResourceName: "elasticstack_kibana_security_exception_list.test",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccResourceExceptionListWithSpace(t *testing.T) {
resourceName := "elasticstack_kibana_security_exception_list.test"
spaceResourceName := "elasticstack_kibana_space.test"
spaceID := fmt.Sprintf("test-space-%s", uuid.New().String()[:8])

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minExceptionListAPISupport),
ConfigDirectory: acctest.NamedTestCaseDirectory("create"),
ConfigVariables: config.Variables{
"space_id": config.StringVariable(spaceID),
"list_id": config.StringVariable("test-exception-list-space"),
"name": config.StringVariable("Test Exception List in Space"),
"description": config.StringVariable("Test exception list in custom space"),
"type": config.StringVariable("detection"),
"namespace_type": config.StringVariable("single"),
"tags": config.ListVariable(config.StringVariable("test"), config.StringVariable("space")),
},
Check: resource.ComposeTestCheckFunc(
// Check space attributes
resource.TestCheckResourceAttr(spaceResourceName, "space_id", spaceID),
resource.TestCheckResourceAttr(spaceResourceName, "name", "Test Space for Exception Lists"),

// Check exception list attributes
resource.TestCheckResourceAttr(resourceName, "space_id", spaceID),
resource.TestCheckResourceAttr(resourceName, "list_id", "test-exception-list-space"),
resource.TestCheckResourceAttr(resourceName, "name", "Test Exception List in Space"),
resource.TestCheckResourceAttr(resourceName, "description", "Test exception list in custom space"),
resource.TestCheckResourceAttr(resourceName, "type", "detection"),
resource.TestCheckResourceAttr(resourceName, "namespace_type", "single"),
resource.TestCheckResourceAttr(resourceName, "tags.0", "test"),
resource.TestCheckResourceAttr(resourceName, "tags.1", "space"),
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttrSet(resourceName, "created_at"),
resource.TestCheckResourceAttrSet(resourceName, "created_by"),
),
},
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minExceptionListAPISupport),
ConfigDirectory: acctest.NamedTestCaseDirectory("update"),
ConfigVariables: config.Variables{
"space_id": config.StringVariable(spaceID),
"list_id": config.StringVariable("test-exception-list-space"),
"name": config.StringVariable("Test Exception List in Space Updated"),
"description": config.StringVariable("Updated description in space"),
"type": config.StringVariable("detection"),
"namespace_type": config.StringVariable("single"),
"tags": config.ListVariable(config.StringVariable("test"), config.StringVariable("space"), config.StringVariable("updated")),
},
Check: resource.ComposeTestCheckFunc(
// Check space attributes remain the same
resource.TestCheckResourceAttr(spaceResourceName, "space_id", spaceID),
resource.TestCheckResourceAttr(spaceResourceName, "name", "Test Space for Exception Lists"),

// Check updated exception list attributes
resource.TestCheckResourceAttr(resourceName, "space_id", spaceID),
resource.TestCheckResourceAttr(resourceName, "name", "Test Exception List in Space Updated"),
resource.TestCheckResourceAttr(resourceName, "description", "Updated description in space"),
resource.TestCheckResourceAttr(resourceName, "tags.0", "test"),
resource.TestCheckResourceAttr(resourceName, "tags.1", "space"),
resource.TestCheckResourceAttr(resourceName, "tags.2", "updated"),
),
},
{ // Import
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minExceptionListAPISupport),
ConfigDirectory: acctest.NamedTestCaseDirectory("update"),
ConfigVariables: config.Variables{
"space_id": config.StringVariable(spaceID),
"list_id": config.StringVariable("test-exception-list-space"),
"name": config.StringVariable("Test Exception List in Space Updated"),
"description": config.StringVariable("Updated description in space"),
"type": config.StringVariable("detection"),
"namespace_type": config.StringVariable("single"),
"tags": config.ListVariable(config.StringVariable("test"), config.StringVariable("space"), config.StringVariable("updated")),
},
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Loading
Loading