Skip to content

Commit 65879ca

Browse files
committed
(TODO remove) Add security list
1 parent 1847c2a commit 65879ca

File tree

24 files changed

+5709
-4533
lines changed

24 files changed

+5709
-4533
lines changed

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ FLEET_CONTAINER_NAME=terraform-elasticstack-fleet
1515
ACCEPTANCE_TESTS_CONTAINER_NAME=terraform-elasticstack-acceptance-tests
1616
TOKEN_ACCEPTANCE_TESTS_CONTAINER_NAME=terraform-elasticstack-token-acceptance-tests
1717
GOVERSION=1.25.1
18+
TF_ELASTICSTACK_INCLUDE_EXPERIMENTAL=true

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ jobs:
5555
ELASTIC_PASSWORD: password
5656
KIBANA_SYSTEM_USERNAME: kibana_system
5757
KIBANA_SYSTEM_PASSWORD: password
58+
TF_ELASTICSTACK_INCLUDE_EXPERIMENTAL: true
5859
services:
5960
elasticsearch:
6061
image: docker.elastic.co/elasticsearch/elasticsearch:${{ matrix.version }}

CODING_STANDARDS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ This document outlines the coding standards and conventions used in the terrafor
3636
- Prefer using existing util functions over longer form, duplicated code:
3737
- `utils.IsKnown(val)` instead of `!val.IsNull() && !val.IsUnknown()`
3838
- `utils.ListTypeAs` instead of `val.ElementsAs` or similar for other collection types
39+
- The final state for a resource should be derived from a read request following a mutative request (eg create or update). We should not use the response from a mutative request to build the final resource state.
3940

4041
## Schema Definitions
4142

docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ services:
134134
ELASTICSEARCH_USERNAME: elastic
135135
ELASTICSEARCH_PASSWORD: ${ELASTICSEARCH_PASSWORD}
136136
TF_LOG: ${TF_LOG:-info}
137-
command: make testacc TESTARGS=${TESTARGS:-}
137+
TF_ELASTICSTACK_INCLUDE_EXPERIMENTAL: "true"
138+
command: make testacc TESTARGS='${TESTARGS:-}'
138139

139140
token-acceptance-tests:
140141
profiles: ["token-acceptance-tests"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
resource "elasticstack_kibana_security_list" "ip_list" {
2+
space_id = "default"
3+
name = "Trusted IP Addresses"
4+
description = "List of trusted IP addresses for security rules"
5+
type = "ip"
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
resource "elasticstack_kibana_security_list" "keyword_list" {
2+
space_id = "security"
3+
list_id = "custom-keywords"
4+
name = "Custom Keywords"
5+
description = "Custom keyword list for detection rules"
6+
type = "keyword"
7+
}

generated/kbapi/kibana.gen.go

Lines changed: 4679 additions & 4532 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

generated/kbapi/transform_schema.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,11 @@ func transformKibanaPaths(schema *Schema) {
691691
"/api/actions/connector/{id}",
692692
"/api/actions/connectors",
693693
"/api/detection_engine/rules",
694+
"/api/exception_lists",
695+
"/api/exception_lists/items",
696+
"/api/lists",
697+
"/api/lists/index",
698+
"/api/lists/items",
694699
}
695700

696701
// Add a spaceId parameter if not already present
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package kibana_oapi
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
8+
"github.com/elastic/terraform-provider-elasticstack/internal/diagutil"
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
10+
)
11+
12+
// CreateListIndex creates the .lists and .items data streams for a space if they don't exist.
13+
// This is required before any list operations can be performed.
14+
func CreateListIndex(ctx context.Context, client *Client, spaceId string) diag.Diagnostics {
15+
resp, err := client.API.CreateListIndexWithResponse(ctx, kbapi.SpaceId(spaceId))
16+
if err != nil {
17+
return diagutil.FrameworkDiagFromError(err)
18+
}
19+
20+
switch resp.StatusCode() {
21+
case http.StatusOK:
22+
return nil
23+
default:
24+
return reportUnknownError(resp.StatusCode(), resp.Body)
25+
}
26+
}
27+
28+
// GetList reads a security list from the API by ID
29+
func GetList(ctx context.Context, client *Client, spaceId string, params *kbapi.ReadListParams) (*kbapi.ReadListResponse, diag.Diagnostics) {
30+
resp, err := client.API.ReadListWithResponse(ctx, kbapi.SpaceId(spaceId), params)
31+
if err != nil {
32+
return nil, diagutil.FrameworkDiagFromError(err)
33+
}
34+
35+
switch resp.StatusCode() {
36+
case http.StatusOK:
37+
return resp, nil
38+
case http.StatusNotFound:
39+
return nil, nil
40+
default:
41+
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
42+
}
43+
}
44+
45+
// CreateList creates a new security list.
46+
func CreateList(ctx context.Context, client *Client, spaceId string, body kbapi.CreateListJSONRequestBody) (*kbapi.CreateListResponse, diag.Diagnostics) {
47+
resp, err := client.API.CreateListWithResponse(ctx, kbapi.SpaceId(spaceId), body)
48+
if err != nil {
49+
return nil, diagutil.FrameworkDiagFromError(err)
50+
}
51+
52+
switch resp.StatusCode() {
53+
case http.StatusOK:
54+
return resp, nil
55+
default:
56+
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
57+
}
58+
}
59+
60+
// UpdateList updates an existing security list.
61+
func UpdateList(ctx context.Context, client *Client, spaceId string, body kbapi.UpdateListJSONRequestBody) (*kbapi.UpdateListResponse, diag.Diagnostics) {
62+
resp, err := client.API.UpdateListWithResponse(ctx, kbapi.SpaceId(spaceId), body)
63+
if err != nil {
64+
return nil, diagutil.FrameworkDiagFromError(err)
65+
}
66+
67+
switch resp.StatusCode() {
68+
case http.StatusOK:
69+
return resp, nil
70+
default:
71+
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
72+
}
73+
}
74+
75+
// DeleteList deletes an existing security list.
76+
func DeleteList(ctx context.Context, client *Client, spaceId string, params *kbapi.DeleteListParams) diag.Diagnostics {
77+
resp, err := client.API.DeleteListWithResponse(ctx, kbapi.SpaceId(spaceId), params)
78+
if err != nil {
79+
return diagutil.FrameworkDiagFromError(err)
80+
}
81+
82+
switch resp.StatusCode() {
83+
case http.StatusOK:
84+
return nil
85+
case http.StatusNotFound:
86+
return nil
87+
default:
88+
return reportUnknownError(resp.StatusCode(), resp.Body)
89+
}
90+
}
91+
92+
// GetListItem reads a security list item from the API by ID or list_id and value
93+
func GetListItem(ctx context.Context, client *Client, spaceId string, params *kbapi.ReadListItemParams) (*kbapi.ReadListItemResponse, diag.Diagnostics) {
94+
resp, err := client.API.ReadListItemWithResponse(ctx, kbapi.SpaceId(spaceId), params)
95+
if err != nil {
96+
return nil, diagutil.FrameworkDiagFromError(err)
97+
}
98+
99+
switch resp.StatusCode() {
100+
case http.StatusOK:
101+
return resp, nil
102+
case http.StatusNotFound:
103+
return nil, nil
104+
default:
105+
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
106+
}
107+
}
108+
109+
// CreateListItem creates a new security list item.
110+
func CreateListItem(ctx context.Context, client *Client, spaceId string, body kbapi.CreateListItemJSONRequestBody) (*kbapi.CreateListItemResponse, diag.Diagnostics) {
111+
resp, err := client.API.CreateListItemWithResponse(ctx, kbapi.SpaceId(spaceId), body)
112+
if err != nil {
113+
return nil, diagutil.FrameworkDiagFromError(err)
114+
}
115+
116+
switch resp.StatusCode() {
117+
case http.StatusOK:
118+
return resp, nil
119+
default:
120+
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
121+
}
122+
}
123+
124+
// UpdateListItem updates an existing security list item.
125+
func UpdateListItem(ctx context.Context, client *Client, spaceId string, body kbapi.UpdateListItemJSONRequestBody) (*kbapi.UpdateListItemResponse, diag.Diagnostics) {
126+
resp, err := client.API.UpdateListItemWithResponse(ctx, kbapi.SpaceId(spaceId), body)
127+
if err != nil {
128+
return nil, diagutil.FrameworkDiagFromError(err)
129+
}
130+
131+
switch resp.StatusCode() {
132+
case http.StatusOK:
133+
return resp, nil
134+
default:
135+
return nil, reportUnknownError(resp.StatusCode(), resp.Body)
136+
}
137+
}
138+
139+
// DeleteListItem deletes an existing security list item.
140+
func DeleteListItem(ctx context.Context, client *Client, spaceId string, params *kbapi.DeleteListItemParams) diag.Diagnostics {
141+
resp, err := client.API.DeleteListItemWithResponse(ctx, kbapi.SpaceId(spaceId), params)
142+
if err != nil {
143+
return diagutil.FrameworkDiagFromError(err)
144+
}
145+
146+
switch resp.StatusCode() {
147+
case http.StatusOK:
148+
return nil
149+
case http.StatusNotFound:
150+
return nil
151+
default:
152+
return reportUnknownError(resp.StatusCode(), resp.Body)
153+
}
154+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package security_list_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
8+
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
9+
"github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana_oapi"
10+
"github.com/google/uuid"
11+
"github.com/hashicorp/terraform-plugin-testing/config"
12+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
13+
)
14+
15+
func ensureListIndexExists(t *testing.T) {
16+
client, err := clients.NewAcceptanceTestingClient()
17+
if err != nil {
18+
t.Fatalf("Failed to create client: %v", err)
19+
}
20+
21+
kibanaClient, err := client.GetKibanaOapiClient()
22+
if err != nil {
23+
t.Fatalf("Failed to get Kibana client: %v", err)
24+
}
25+
26+
diags := kibana_oapi.CreateListIndex(context.Background(), kibanaClient, "default")
27+
if diags.HasError() {
28+
// It's OK if it already exists, we'll only fail on other errors
29+
for _, d := range diags {
30+
if d.Summary() != "Unexpected status code from server: got HTTP 409" {
31+
t.Fatalf("Failed to create list index: %v", d.Detail())
32+
}
33+
}
34+
}
35+
}
36+
37+
func TestAccResourceSecurityList(t *testing.T) {
38+
listID := "test-list-" + uuid.New().String()
39+
resource.Test(t, resource.TestCase{
40+
PreCheck: func() {
41+
acctest.PreCheck(t)
42+
ensureListIndexExists(t)
43+
},
44+
ProtoV6ProviderFactories: acctest.Providers,
45+
Steps: []resource.TestStep{
46+
{ // Create
47+
ConfigDirectory: acctest.NamedTestCaseDirectory("create"),
48+
ConfigVariables: config.Variables{
49+
"list_id": config.StringVariable(listID),
50+
"name": config.StringVariable("Test Security List"),
51+
"description": config.StringVariable("A test security list for IP addresses"),
52+
"type": config.StringVariable("ip"),
53+
},
54+
Check: resource.ComposeTestCheckFunc(
55+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list.test", "id"),
56+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "name", "Test Security List"),
57+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "description", "A test security list for IP addresses"),
58+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "type", "ip"),
59+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list.test", "created_at"),
60+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list.test", "created_by"),
61+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list.test", "updated_at"),
62+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list.test", "updated_by"),
63+
),
64+
},
65+
{ // Update
66+
ConfigDirectory: acctest.NamedTestCaseDirectory("update"),
67+
ConfigVariables: config.Variables{
68+
"list_id": config.StringVariable(listID),
69+
"name": config.StringVariable("Updated Security List"),
70+
"description": config.StringVariable("An updated test security list"),
71+
"type": config.StringVariable("ip"),
72+
},
73+
Check: resource.ComposeTestCheckFunc(
74+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "name", "Updated Security List"),
75+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "description", "An updated test security list"),
76+
),
77+
},
78+
},
79+
})
80+
}
81+
82+
func TestAccResourceSecurityList_KeywordType(t *testing.T) {
83+
listID := "keyword-list-" + uuid.New().String()
84+
resource.Test(t, resource.TestCase{
85+
PreCheck: func() {
86+
acctest.PreCheck(t)
87+
ensureListIndexExists(t)
88+
},
89+
ProtoV6ProviderFactories: acctest.Providers,
90+
Steps: []resource.TestStep{
91+
{
92+
ConfigDirectory: acctest.NamedTestCaseDirectory("keyword_type"),
93+
ConfigVariables: config.Variables{
94+
"list_id": config.StringVariable(listID),
95+
"name": config.StringVariable("Keyword Security List"),
96+
"description": config.StringVariable("A test security list for keywords"),
97+
"type": config.StringVariable("keyword"),
98+
},
99+
Check: resource.ComposeTestCheckFunc(
100+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "type", "keyword"),
101+
),
102+
},
103+
},
104+
})
105+
}
106+
107+
func TestAccResourceSecurityList_SerializerDeserializer(t *testing.T) {
108+
listID := "serializer-list-" + uuid.New().String()
109+
resource.Test(t, resource.TestCase{
110+
PreCheck: func() {
111+
acctest.PreCheck(t)
112+
ensureListIndexExists(t)
113+
},
114+
ProtoV6ProviderFactories: acctest.Providers,
115+
Steps: []resource.TestStep{
116+
{ // Create with serializer and deserializer
117+
ConfigDirectory: acctest.NamedTestCaseDirectory("create"),
118+
ConfigVariables: config.Variables{
119+
"list_id": config.StringVariable(listID),
120+
"name": config.StringVariable("Custom Serializer List"),
121+
"description": config.StringVariable("A test list with custom serializer and deserializer"),
122+
"type": config.StringVariable("ip"),
123+
"serializer": config.StringVariable("{{ip}}"),
124+
"deserializer": config.StringVariable("{{ip}}"),
125+
},
126+
Check: resource.ComposeTestCheckFunc(
127+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list.test", "id"),
128+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "name", "Custom Serializer List"),
129+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "type", "ip"),
130+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "serializer", "{{ip}}"),
131+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "deserializer", "{{ip}}"),
132+
),
133+
},
134+
{ // Update name and description (serializer/deserializer are immutable)
135+
ConfigDirectory: acctest.NamedTestCaseDirectory("update"),
136+
ConfigVariables: config.Variables{
137+
"list_id": config.StringVariable(listID),
138+
"name": config.StringVariable("Updated Serializer List"),
139+
"description": config.StringVariable("Updated test list description"),
140+
"type": config.StringVariable("ip"),
141+
"serializer": config.StringVariable("{{ip}}"),
142+
"deserializer": config.StringVariable("{{ip}}"),
143+
},
144+
Check: resource.ComposeTestCheckFunc(
145+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "name", "Updated Serializer List"),
146+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "description", "Updated test list description"),
147+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "serializer", "{{ip}}"),
148+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "deserializer", "{{ip}}"),
149+
),
150+
},
151+
},
152+
})
153+
}

0 commit comments

Comments
 (0)