diff --git a/CHANGELOG.md b/CHANGELOG.md
index 811060fb2c..a14b5cc7e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
- Add the `alert_delay` field to the Create Rule API ([#715](https://github.com/elastic/terraform-provider-elasticstack/pull/715))
- Add support for data_stream `lifecycle` template settings ([#724](https://github.com/elastic/terraform-provider-elasticstack/pull/724))
- Fix a provider panic when `elasticstack_kibana_action_connector` reads a non-existant connector ([#729](https://github.com/elastic/terraform-provider-elasticstack/pull/729))
+- Add support for `remote_indicies` to `elasticstack_elasticsearch_security_role` & `elasticstack_kibana_security_role` (#723)[https://github.com/elastic/terraform-provider-elasticstack/pull/723]
## [0.11.6] - 2024-08-20
diff --git a/docs/data-sources/elasticsearch_security_role.md b/docs/data-sources/elasticsearch_security_role.md
index 8e39174b8a..6e917b6743 100644
--- a/docs/data-sources/elasticsearch_security_role.md
+++ b/docs/data-sources/elasticsearch_security_role.md
@@ -46,6 +46,7 @@ output "role" {
- `id` (String) Internal identifier of the resource
- `indices` (Set of Object) A list of indices permissions entries. (see [below for nested schema](#nestedatt--indices))
- `metadata` (String) Optional meta-data.
+- `remote_indices` (Set of Object) A list of remote indices permissions entries. Remote indices are effective for remote clusters configured with the API key based model. They have no effect for remote clusters configured with the certificate based model. (see [below for nested schema](#nestedatt--remote_indices))
### Nested Schema for `elasticsearch_connection`
@@ -95,3 +96,24 @@ Read-Only:
- `except` (Set of String)
- `grant` (Set of String)
+
+
+
+
+### Nested Schema for `remote_indices`
+
+Read-Only:
+
+- `clusters` (Set of String)
+- `field_security` (List of Object) (see [below for nested schema](#nestedobjatt--remote_indices--field_security))
+- `names` (Set of String)
+- `privileges` (Set of String)
+- `query` (String)
+
+
+### Nested Schema for `remote_indices.field_security`
+
+Read-Only:
+
+- `except` (Set of String)
+- `grant` (Set of String)
diff --git a/docs/data-sources/kibana_security_role.md b/docs/data-sources/kibana_security_role.md
index 78c1a1ed0d..58904b2f83 100644
--- a/docs/data-sources/kibana_security_role.md
+++ b/docs/data-sources/kibana_security_role.md
@@ -47,6 +47,7 @@ Read-Only:
- `cluster` (Set of String)
- `indices` (Set of Object) (see [below for nested schema](#nestedobjatt--elasticsearch--indices))
+- `remote_indices` (Set of Object) (see [below for nested schema](#nestedobjatt--elasticsearch--remote_indices))
- `run_as` (Set of String)
@@ -69,6 +70,27 @@ Read-Only:
+
+### Nested Schema for `elasticsearch.remote_indices`
+
+Read-Only:
+
+- `clusters` (Set of String)
+- `field_security` (List of Object) (see [below for nested schema](#nestedobjatt--elasticsearch--remote_indices--field_security))
+- `names` (Set of String)
+- `privileges` (Set of String)
+- `query` (String)
+
+
+### Nested Schema for `elasticsearch.remote_indices.field_security`
+
+Read-Only:
+
+- `except` (Set of String)
+- `grant` (Set of String)
+
+
+
### Nested Schema for `kibana`
diff --git a/docs/resources/elasticsearch_security_role.md b/docs/resources/elasticsearch_security_role.md
index 646548536d..bddcc1cda2 100644
--- a/docs/resources/elasticsearch_security_role.md
+++ b/docs/resources/elasticsearch_security_role.md
@@ -59,6 +59,7 @@ output "role" {
- `global` (String) An object defining global privileges.
- `indices` (Block Set) A list of indices permissions entries. (see [below for nested schema](#nestedblock--indices))
- `metadata` (String) Optional meta-data.
+- `remote_indices` (Block Set) A list of remote indices permissions entries. Remote indices are effective for remote clusters configured with the API key based model. They have no effect for remote clusters configured with the certificate based model. (see [below for nested schema](#nestedblock--remote_indices))
- `run_as` (Set of String) A list of users that the owners of this role can impersonate.
### Read-Only
@@ -117,6 +118,30 @@ Optional:
- `except` (Set of String) List of the fields to which the grants will not be applied.
- `grant` (Set of String) List of the fields to grant the access to.
+
+
+
+### Nested Schema for `remote_indices`
+
+Required:
+
+- `clusters` (Set of String) A list of cluster aliases to which the permissions in this entry apply.
+- `names` (Set of String) A list of indices (or index name patterns) to which the permissions in this entry apply.
+- `privileges` (Set of String) The index level privileges that the owners of the role have on the specified indices.
+
+Optional:
+
+- `field_security` (Block List, Max: 1) The document fields that the owners of the role have read access to. (see [below for nested schema](#nestedblock--remote_indices--field_security))
+- `query` (String) A search query that defines the documents the owners of the role have read access to.
+
+
+### Nested Schema for `remote_indices.field_security`
+
+Optional:
+
+- `except` (Set of String) List of the fields to which the grants will not be applied.
+- `grant` (Set of String) List of the fields to grant the access to.
+
## Import
Import is supported using the following syntax:
diff --git a/docs/resources/kibana_security_role.md b/docs/resources/kibana_security_role.md
index 45ab259898..77c2bb0217 100644
--- a/docs/resources/kibana_security_role.md
+++ b/docs/resources/kibana_security_role.md
@@ -32,6 +32,15 @@ resource "elasticstack_kibana_security_role" "example" {
names = ["test"]
privileges = ["create", "read", "write"]
}
+ remote_indices {
+ field_security {
+ grant = ["test"]
+ except = []
+ }
+ names = ["test"]
+ clusters = ["test-cluster"]
+ privileges = ["create", "read", "write"]
+ }
}
kibana {
base = ["all"]
@@ -60,6 +69,15 @@ resource "elasticstack_kibana_security_role" "example" {
names = ["test"]
privileges = ["create", "read", "write"]
}
+ remote_indices {
+ field_security {
+ grant = ["test"]
+ except = []
+ }
+ names = ["test"]
+ clusters = ["test-cluster"]
+ privileges = ["create", "read", "write"]
+ }
}
kibana {
feature {
@@ -116,6 +134,7 @@ Optional:
- `cluster` (Set of String) List of the cluster privileges.
- `indices` (Block Set) A list of indices permissions entries. (see [below for nested schema](#nestedblock--elasticsearch--indices))
+- `remote_indices` (Block Set) A list of remote indices permissions entries. Remote indices are effective for remote clusters configured with the API key based model. They have no effect for remote clusters configured with the certificate based model. (see [below for nested schema](#nestedblock--elasticsearch--remote_indices))
- `run_as` (Set of String) A list of usernames the owners of this role can impersonate.
@@ -141,6 +160,30 @@ Optional:
+
+### Nested Schema for `elasticsearch.remote_indices`
+
+Required:
+
+- `clusters` (Set of String) A list of cluster aliases to which the permissions in this entry apply.
+- `names` (Set of String) A list of indices (or index name patterns) to which the permissions in this entry apply.
+- `privileges` (Set of String) The index level privileges that the owners of the role have on the specified indices.
+
+Optional:
+
+- `field_security` (Block List, Max: 1) The document fields that the owners of the role have read access to. (see [below for nested schema](#nestedblock--elasticsearch--remote_indices--field_security))
+- `query` (String) A search query that defines the documents the owners of the role have read access to.
+
+
+### Nested Schema for `elasticsearch.remote_indices.field_security`
+
+Optional:
+
+- `except` (Set of String) List of the fields to which the grants will not be applied.
+- `grant` (Set of String) List of the fields to grant the access to.
+
+
+
### Nested Schema for `kibana`
diff --git a/examples/resources/elasticstack_kibana_security_role/resource-with-base.tf b/examples/resources/elasticstack_kibana_security_role/resource-with-base.tf
index 6e2b4f8cb2..f56651816f 100644
--- a/examples/resources/elasticstack_kibana_security_role/resource-with-base.tf
+++ b/examples/resources/elasticstack_kibana_security_role/resource-with-base.tf
@@ -16,6 +16,15 @@ resource "elasticstack_kibana_security_role" "example" {
names = ["test"]
privileges = ["create", "read", "write"]
}
+ remote_indices {
+ field_security {
+ grant = ["test"]
+ except = []
+ }
+ names = ["test"]
+ clusters = ["test-cluster"]
+ privileges = ["create", "read", "write"]
+ }
}
kibana {
base = ["all"]
diff --git a/examples/resources/elasticstack_kibana_security_role/resource-with-feature.tf b/examples/resources/elasticstack_kibana_security_role/resource-with-feature.tf
index b94ef0b8fd..d54e75bf51 100644
--- a/examples/resources/elasticstack_kibana_security_role/resource-with-feature.tf
+++ b/examples/resources/elasticstack_kibana_security_role/resource-with-feature.tf
@@ -16,6 +16,15 @@ resource "elasticstack_kibana_security_role" "example" {
names = ["test"]
privileges = ["create", "read", "write"]
}
+ remote_indices {
+ field_security {
+ grant = ["test"]
+ except = []
+ }
+ names = ["test"]
+ clusters = ["test-cluster"]
+ privileges = ["create", "read", "write"]
+ }
}
kibana {
feature {
diff --git a/internal/elasticsearch/security/role.go b/internal/elasticsearch/security/role.go
index 3b886753c8..dde719db09 100644
--- a/internal/elasticsearch/security/role.go
+++ b/internal/elasticsearch/security/role.go
@@ -10,12 +10,15 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch"
"github.com/elastic/terraform-provider-elasticstack/internal/models"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
+ "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
+var minSupportedRemoteIndicesVersion = version.Must(version.NewVersion("8.10.0"))
+
func ResourceRole() *schema.Resource {
roleSchema := map[string]*schema.Schema{
"id": {
@@ -137,6 +140,72 @@ func ResourceRole() *schema.Resource {
},
},
},
+ "remote_indices": {
+ Description: "A list of remote indices permissions entries. Remote indices are effective for remote clusters configured with the API key based model. They have no effect for remote clusters configured with the certificate based model.",
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "clusters": {
+ Description: "A list of cluster aliases to which the permissions in this entry apply.",
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "field_security": {
+ Description: "The document fields that the owners of the role have read access to.",
+ Type: schema.TypeList,
+ Optional: true,
+ MaxItems: 1,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "grant": {
+ Description: "List of the fields to grant the access to.",
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "except": {
+ Description: "List of the fields to which the grants will not be applied.",
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ },
+ },
+ },
+ "query": {
+ Description: "A search query that defines the documents the owners of the role have read access to.",
+ Type: schema.TypeString,
+ ValidateFunc: validation.StringIsJSON,
+ DiffSuppressFunc: utils.DiffJsonSuppress,
+ Optional: true,
+ },
+ "names": {
+ Description: "A list of indices (or index name patterns) to which the permissions in this entry apply.",
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "privileges": {
+ Description: "The index level privileges that the owners of the role have on the specified indices.",
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ },
+ },
+ },
"metadata": {
Description: "Optional meta-data.",
Type: schema.TypeString,
@@ -178,6 +247,10 @@ func resourceSecurityRolePut(ctx context.Context, d *schema.ResourceData, meta i
if diags.HasError() {
return diags
}
+ serverVersion, diags := client.ServerVersion(ctx)
+ if diags.HasError() {
+ return diags
+ }
roleId := d.Get("name").(string)
id, diags := client.ID(ctx, roleId)
if diags.HasError() {
@@ -286,6 +359,69 @@ func resourceSecurityRolePut(ctx context.Context, d *schema.ResourceData, meta i
role.Indices = indices
}
+ if v, ok := d.GetOk("remote_indices"); ok {
+ definedRemoteIndices := v.(*schema.Set)
+ if definedRemoteIndices.Len() > 0 && serverVersion.LessThan(minSupportedRemoteIndicesVersion) {
+ return diag.FromErr(fmt.Errorf("'remote_indices' is supported only for Elasticsearch v%s and above", minSupportedRemoteIndicesVersion.String()))
+ }
+ remote_indices := make([]models.RemoteIndexPerms, definedRemoteIndices.Len())
+ for i, idx := range definedRemoteIndices.List() {
+ remote_index := idx.(map[string]interface{})
+
+ definedRemoteNames := remote_index["names"].(*schema.Set)
+ remote_names := make([]string, definedRemoteNames.Len())
+ for i, name := range definedRemoteNames.List() {
+ remote_names[i] = name.(string)
+ }
+ definedRemoteClusters := remote_index["clusters"].(*schema.Set)
+ remote_clusters := make([]string, definedRemoteClusters.Len())
+ for i, cluster := range definedRemoteClusters.List() {
+ remote_clusters[i] = cluster.(string)
+ }
+ definedRemotePrivs := remote_index["privileges"].(*schema.Set)
+ remote_privs := make([]string, definedRemotePrivs.Len())
+ for i, pr := range definedRemotePrivs.List() {
+ remote_privs[i] = pr.(string)
+ }
+
+ newRemoteIndex := models.RemoteIndexPerms{
+ Names: remote_names,
+ Clusters: remote_clusters,
+ Privileges: remote_privs,
+ }
+
+ if query := remote_index["query"].(string); query != "" {
+ newRemoteIndex.Query = &query
+ }
+ if fieldSec := remote_index["field_security"].([]interface{}); len(fieldSec) > 0 {
+ remote_fieldSecurity := models.FieldSecurity{}
+ // there must be only 1 entry
+ definedRemoteFieldSec := fieldSec[0].(map[string]interface{})
+
+ // grants
+ if gr := definedRemoteFieldSec["grant"].(*schema.Set); gr != nil {
+ grants := make([]string, gr.Len())
+ for i, grant := range gr.List() {
+ grants[i] = grant.(string)
+ }
+ remote_fieldSecurity.Grant = grants
+ }
+ // except
+ if exp := definedRemoteFieldSec["except"].(*schema.Set); exp != nil {
+ excepts := make([]string, exp.Len())
+ for i, except := range exp.List() {
+ excepts[i] = except.(string)
+ }
+ remote_fieldSecurity.Except = excepts
+ }
+ newRemoteIndex.FieldSecurity = &remote_fieldSecurity
+ }
+
+ remote_indices[i] = newRemoteIndex
+ }
+ role.RemoteIndices = remote_indices
+ }
+
if v, ok := d.GetOk("metadata"); ok {
metadata := make(map[string]interface{})
if err := json.NewDecoder(strings.NewReader(v.(string))).Decode(&metadata); err != nil {
@@ -357,11 +493,14 @@ func resourceSecurityRoleRead(ctx context.Context, d *schema.ResourceData, meta
}
}
- indexes := role.Indices
- indices := flattenIndicesData(&indexes)
+ indices := flattenIndicesData(role.Indices)
if err := d.Set("indices", indices); err != nil {
return diag.FromErr(err)
}
+ remoteIndices := flattenRemoteIndicesData(role.RemoteIndices)
+ if err := d.Set("remote_indices", remoteIndices); err != nil {
+ return diag.FromErr(err)
+ }
if role.Metadata != nil {
metadata, err := json.Marshal(role.Metadata)
@@ -395,28 +534,46 @@ func flattenApplicationsData(apps *[]models.Application) []interface{} {
return make([]interface{}, 0)
}
-func flattenIndicesData(indices *[]models.IndexPerms) []interface{} {
- if indices != nil {
- oindx := make([]interface{}, len(*indices))
-
- for i, index := range *indices {
- oi := make(map[string]interface{})
- oi["names"] = index.Names
- oi["privileges"] = index.Privileges
- oi["query"] = index.Query
- oi["allow_restricted_indices"] = index.AllowRestrictedIndices
-
- if index.FieldSecurity != nil {
- fsec := make(map[string]interface{})
- fsec["grant"] = index.FieldSecurity.Grant
- fsec["except"] = index.FieldSecurity.Except
- oi["field_security"] = []interface{}{fsec}
- }
- oindx[i] = oi
+func flattenIndicesData(indices []models.IndexPerms) []interface{} {
+ oindx := make([]interface{}, len(indices))
+
+ for i, index := range indices {
+ oi := make(map[string]interface{})
+ oi["names"] = index.Names
+ oi["privileges"] = index.Privileges
+ oi["query"] = index.Query
+ oi["allow_restricted_indices"] = index.AllowRestrictedIndices
+
+ if index.FieldSecurity != nil {
+ fsec := make(map[string]interface{})
+ fsec["grant"] = index.FieldSecurity.Grant
+ fsec["except"] = index.FieldSecurity.Except
+ oi["field_security"] = []interface{}{fsec}
}
- return oindx
+ oindx[i] = oi
}
- return make([]interface{}, 0)
+ return oindx
+}
+
+func flattenRemoteIndicesData(remoteIndices []models.RemoteIndexPerms) []interface{} {
+ oRemoteIndx := make([]interface{}, len(remoteIndices))
+
+ for i, remoteIndex := range remoteIndices {
+ oi := make(map[string]interface{})
+ oi["names"] = remoteIndex.Names
+ oi["clusters"] = remoteIndex.Clusters
+ oi["privileges"] = remoteIndex.Privileges
+ oi["query"] = remoteIndex.Query
+
+ if remoteIndex.FieldSecurity != nil {
+ fsec := make(map[string]interface{})
+ fsec["grant"] = remoteIndex.FieldSecurity.Grant
+ fsec["except"] = remoteIndex.FieldSecurity.Except
+ oi["field_security"] = []interface{}{fsec}
+ }
+ oRemoteIndx[i] = oi
+ }
+ return oRemoteIndx
}
func resourceSecurityRoleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
diff --git a/internal/elasticsearch/security/role_data_source.go b/internal/elasticsearch/security/role_data_source.go
index cc263de74b..fe8933a856 100644
--- a/internal/elasticsearch/security/role_data_source.go
+++ b/internal/elasticsearch/security/role_data_source.go
@@ -124,6 +124,69 @@ func DataSourceRole() *schema.Resource {
},
},
},
+ "remote_indices": {
+ Description: "A list of remote indices permissions entries. Remote indices are effective for remote clusters configured with the API key based model. They have no effect for remote clusters configured with the certificate based model.",
+ Type: schema.TypeSet,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "clusters": {
+ Description: "A list of cluster aliases to which the permissions in this entry apply.",
+ Type: schema.TypeSet,
+ Computed: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "field_security": {
+ Description: "The document fields that the owners of the role have read access to.",
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "grant": {
+ Description: "List of the fields to grant the access to.",
+ Type: schema.TypeSet,
+ Computed: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "except": {
+ Description: "List of the fields to which the grants will not be applied.",
+ Type: schema.TypeSet,
+ Computed: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ },
+ },
+ },
+ "names": {
+ Description: "A list of indices (or index name patterns) to which the permissions in this entry apply.",
+ Type: schema.TypeSet,
+ Computed: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "privileges": {
+ Description: "The index level privileges that the owners of the role have on the specified indices.",
+ Type: schema.TypeSet,
+ Computed: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "query": {
+ Description: "A search query that defines the documents the owners of the role have read access to.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ },
+ },
"metadata": {
Description: "Optional meta-data.",
Type: schema.TypeString,
diff --git a/internal/elasticsearch/security/role_data_source_test.go b/internal/elasticsearch/security/role_data_source_test.go
index a08e8b11f1..33778129ea 100644
--- a/internal/elasticsearch/security/role_data_source_test.go
+++ b/internal/elasticsearch/security/role_data_source_test.go
@@ -5,10 +5,13 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
+ "github.com/elastic/terraform-provider-elasticstack/internal/versionutils"
+ "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)
func TestAccDataSourceSecurityRole(t *testing.T) {
+ minSupportedRemoteIndicesVersion := version.Must(version.NewSemver("8.10.0"))
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.Providers,
@@ -28,6 +31,24 @@ func TestAccDataSourceSecurityRole(t *testing.T) {
resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "metadata", `{"version":1}`),
),
},
+ {
+ Config: testAccDataSourceSecurityRoleRemoteIndices,
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedRemoteIndicesVersion),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "name", "data_source_test"),
+ resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "cluster.*", "all"),
+ utils.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.names", []string{"index1", "index2"}),
+ resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.privileges.*", "all"),
+ resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "indices.0.allow_restricted_indices", "true"),
+ resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.application", "myapp"),
+ utils.TestCheckResourceListAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.privileges", []string{"admin", "read"}),
+ resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "applications.0.resources.*", "*"),
+ resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "run_as.*", "other_user"),
+ resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_security_role.test", "metadata", `{"version":1}`),
+ resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "remote_indices.*.clusters.*", "test-cluster2"),
+ resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_security_role.test", "remote_indices.*.names.*", "sample2"),
+ ),
+ },
},
})
}
@@ -64,3 +85,46 @@ data "elasticstack_elasticsearch_security_role" "test" {
name = elasticstack_elasticsearch_security_role.test.name
}
`
+
+const testAccDataSourceSecurityRoleRemoteIndices = `
+provider "elasticstack" {
+ elasticsearch {}
+}
+
+resource "elasticstack_elasticsearch_security_role" "test" {
+ name = "data_source_test"
+ cluster = ["all"]
+
+ indices {
+ names = ["index1", "index2"]
+ privileges = ["all"]
+ allow_restricted_indices = true
+ }
+
+ remote_indices {
+ clusters = ["test-cluster2"]
+ field_security {
+ grant = ["sample"]
+ except = []
+ }
+ names = ["sample2"]
+ privileges = ["create", "read", "write"]
+ }
+
+ applications {
+ application = "myapp"
+ privileges = ["admin", "read"]
+ resources = ["*"]
+ }
+
+ run_as = ["other_user"]
+
+ metadata = jsonencode({
+ version = 1
+ })
+}
+
+data "elasticstack_elasticsearch_security_role" "test" {
+ name = elasticstack_elasticsearch_security_role.test.name
+}
+`
diff --git a/internal/elasticsearch/security/role_test.go b/internal/elasticsearch/security/role_test.go
index 4f6382d781..2afb4cc816 100644
--- a/internal/elasticsearch/security/role_test.go
+++ b/internal/elasticsearch/security/role_test.go
@@ -6,6 +6,8 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
+ "github.com/elastic/terraform-provider-elasticstack/internal/versionutils"
+ "github.com/hashicorp/go-version"
sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
@@ -14,6 +16,8 @@ import (
func TestAccResourceSecurityRole(t *testing.T) {
// generate a random username
roleName := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
+ roleNameRemoteIndices := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
+ minSupportedRemoteIndicesVersion := version.Must(version.NewSemver("8.10.0"))
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
@@ -45,6 +49,37 @@ func TestAccResourceSecurityRole(t *testing.T) {
resource.TestCheckNoResourceAttr("elasticstack_elasticsearch_security_role.test", "indices.0.allow_restricted_indices"),
),
},
+ {
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedRemoteIndicesVersion),
+ Config: testAccResourceSecurityRoleRemoteIndicesCreate(roleNameRemoteIndices),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role.test", "name", roleNameRemoteIndices),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role.test", "indices.0.allow_restricted_indices", "true"),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "indices.*.names.*", "index1"),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "indices.*.names.*", "index2"),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "cluster.*", "all"),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "run_as.*", "other_user"),
+ resource.TestCheckNoResourceAttr("elasticstack_elasticsearch_security_role.test", "global"),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "remote_indices.*.clusters.*", "test-cluster"),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "remote_indices.*.names.*", "sample"),
+ ),
+ },
+ {
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedRemoteIndicesVersion),
+ Config: testAccResourceSecurityRoleRemoteIndicesUpdate(roleNameRemoteIndices),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_role.test", "name", roleNameRemoteIndices),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "indices.*.names.*", "index1"),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "indices.*.names.*", "index2"),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "cluster.*", "all"),
+ resource.TestCheckNoResourceAttr("elasticstack_elasticsearch_security_role.test", "run_as.#"),
+ resource.TestCheckNoResourceAttr("elasticstack_elasticsearch_security_role.test", "global"),
+ resource.TestCheckNoResourceAttr("elasticstack_elasticsearch_security_role.test", "applications.#"),
+ resource.TestCheckNoResourceAttr("elasticstack_elasticsearch_security_role.test", "indices.0.allow_restricted_indices"),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "remote_indices.*.clusters.*", "test-cluster2"),
+ resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_security_role.test", "remote_indices.*.names.*", "sample2"),
+ ),
+ },
},
})
}
@@ -102,6 +137,79 @@ resource "elasticstack_elasticsearch_security_role" "test" {
`, roleName)
}
+func testAccResourceSecurityRoleRemoteIndicesCreate(roleName string) string {
+ return fmt.Sprintf(`
+provider "elasticstack" {
+ elasticsearch {}
+}
+
+resource "elasticstack_elasticsearch_security_role" "test" {
+ name = "%s"
+ cluster = ["all"]
+
+ indices {
+ names = ["index1", "index2"]
+ privileges = ["all"]
+ allow_restricted_indices = true
+ }
+
+ remote_indices {
+ clusters = ["test-cluster"]
+ field_security {
+ grant = ["sample"]
+ except = []
+ }
+ names = ["sample"]
+ privileges = ["create", "read", "write"]
+ }
+
+ applications {
+ application = "myapp"
+ privileges = ["admin", "read"]
+ resources = ["*"]
+ }
+
+ run_as = ["other_user"]
+
+ metadata = jsonencode({
+ version = 1
+ })
+}
+ `, roleName)
+}
+
+func testAccResourceSecurityRoleRemoteIndicesUpdate(roleName string) string {
+ return fmt.Sprintf(`
+provider "elasticstack" {
+ elasticsearch {}
+}
+
+resource "elasticstack_elasticsearch_security_role" "test" {
+ name = "%s"
+ cluster = ["all"]
+
+ indices {
+ names = ["index1", "index2"]
+ privileges = ["all"]
+ }
+
+ remote_indices {
+ clusters = ["test-cluster2"]
+ field_security {
+ grant = ["sample"]
+ except = []
+ }
+ names = ["sample2"]
+ privileges = ["create", "read", "write"]
+ }
+
+ metadata = jsonencode({
+ version = 1
+ })
+}
+ `, roleName)
+}
+
func checkResourceSecurityRoleDestroy(s *terraform.State) error {
client, err := clients.NewAcceptanceTestingClient()
if err != nil {
diff --git a/internal/kibana/role.go b/internal/kibana/role.go
index 30e34e8b39..98f0280980 100644
--- a/internal/kibana/role.go
+++ b/internal/kibana/role.go
@@ -9,11 +9,14 @@ import (
"github.com/disaster37/go-kibana-rest/v8/kbapi"
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
+ "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
+var minSupportedRemoteIndicesVersion = version.Must(version.NewVersion("8.10.0"))
+
func ResourceRole() *schema.Resource {
roleSchema := map[string]*schema.Schema{
"name": {
@@ -95,6 +98,72 @@ func ResourceRole() *schema.Resource {
},
},
},
+ "remote_indices": {
+ Description: "A list of remote indices permissions entries. Remote indices are effective for remote clusters configured with the API key based model. They have no effect for remote clusters configured with the certificate based model.",
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "clusters": {
+ Description: "A list of cluster aliases to which the permissions in this entry apply.",
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "field_security": {
+ Description: "The document fields that the owners of the role have read access to.",
+ Type: schema.TypeList,
+ Optional: true,
+ MaxItems: 1,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "grant": {
+ Description: "List of the fields to grant the access to.",
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "except": {
+ Description: "List of the fields to which the grants will not be applied.",
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ },
+ },
+ },
+ "query": {
+ Description: "A search query that defines the documents the owners of the role have read access to.",
+ Type: schema.TypeString,
+ ValidateFunc: validation.StringIsJSON,
+ DiffSuppressFunc: utils.DiffJsonSuppress,
+ Optional: true,
+ },
+ "names": {
+ Description: "A list of indices (or index name patterns) to which the permissions in this entry apply.",
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "privileges": {
+ Description: "The index level privileges that the owners of the role have on the specified indices.",
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ },
+ },
+ },
"run_as": {
Description: "A list of usernames the owners of this role can impersonate.",
Type: schema.TypeSet,
@@ -187,6 +256,11 @@ func resourceRoleUpsert(ctx context.Context, d *schema.ResourceData, meta interf
return diags
}
+ serverVersion, diags := client.ServerVersion(ctx)
+ if diags.HasError() {
+ return diags
+ }
+
kibana, err := client.GetKibanaClient()
if err != nil {
return diag.FromErr(err)
@@ -209,7 +283,10 @@ func resourceRoleUpsert(ctx context.Context, d *schema.ResourceData, meta interf
}
if v, ok := d.GetOk("elasticsearch"); ok {
- kibanaRole.Elasticsearch = expandKibanaRoleElasticsearch(v)
+ kibanaRole.Elasticsearch, diags = expandKibanaRoleElasticsearch(v, serverVersion)
+ if diags != nil {
+ return diags
+ }
}
if v, ok := d.GetOk("metadata"); ok {
@@ -303,8 +380,9 @@ func expandKibanaRoleMetadata(v interface{}) (map[string]interface{}, diag.Diagn
return metadata, nil
}
-func expandKibanaRoleElasticsearch(v interface{}) *kbapi.KibanaRoleElasticsearch {
+func expandKibanaRoleElasticsearch(v interface{}, serverVersion *version.Version) (*kbapi.KibanaRoleElasticsearch, diag.Diagnostics) {
elasticConfig := &kbapi.KibanaRoleElasticsearch{}
+ var diags diag.Diagnostics
if definedElasticConfigs := v.(*schema.Set); definedElasticConfigs.Len() > 0 {
userElasticConfig := definedElasticConfigs.List()[0].(map[string]interface{})
@@ -370,6 +448,71 @@ func expandKibanaRoleElasticsearch(v interface{}) *kbapi.KibanaRoleElasticsearch
elasticConfig.Indices = indices
}
+ if v, ok := userElasticConfig["remote_indices"]; ok {
+ definedRemoteIndices := v.(*schema.Set)
+ if definedRemoteIndices.Len() > 0 {
+ if serverVersion.LessThan(minSupportedRemoteIndicesVersion) {
+ return nil, diag.FromErr(fmt.Errorf("'remote_indices' is supported only for Kibana v%s and above", minSupportedRemoteIndicesVersion.String()))
+ }
+ }
+ remote_indices := make([]kbapi.KibanaRoleElasticsearchRemoteIndice, definedRemoteIndices.Len())
+ for i, idx := range definedRemoteIndices.List() {
+ index := idx.(map[string]interface{})
+
+ definedNames := index["names"].(*schema.Set)
+ names := make([]string, definedNames.Len())
+ for i, name := range definedNames.List() {
+ names[i] = name.(string)
+ }
+ definedClusters := index["clusters"].(*schema.Set)
+ clusters := make([]string, definedClusters.Len())
+ for i, cluster := range definedClusters.List() {
+ clusters[i] = cluster.(string)
+ }
+ definedPrivileges := index["privileges"].(*schema.Set)
+ privileges := make([]string, definedPrivileges.Len())
+ for i, pr := range definedPrivileges.List() {
+ privileges[i] = pr.(string)
+ }
+
+ newRemoteIndex := kbapi.KibanaRoleElasticsearchRemoteIndice{
+ Names: names,
+ Clusters: clusters,
+ Privileges: privileges,
+ }
+
+ if query := index["query"].(string); query != "" {
+ newRemoteIndex.Query = &query
+ }
+ if fieldSec := index["field_security"].([]interface{}); len(fieldSec) > 0 {
+ fieldSecurity := map[string]interface{}{}
+ // there must be only 1 entry
+ definedFieldSec := fieldSec[0].(map[string]interface{})
+
+ // grants
+ if gr := definedFieldSec["grant"].(*schema.Set); gr != nil {
+ grants := make([]string, gr.Len())
+ for i, grant := range gr.List() {
+ grants[i] = grant.(string)
+ }
+ fieldSecurity["grant"] = grants
+ }
+ // except
+ if exp := definedFieldSec["except"].(*schema.Set); exp != nil {
+ excepts := make([]string, exp.Len())
+ for i, except := range exp.List() {
+ excepts[i] = except.(string)
+ }
+ fieldSecurity["except"] = excepts
+ }
+ newRemoteIndex.FieldSecurity = fieldSecurity
+ }
+
+ remote_indices[i] = newRemoteIndex
+ }
+ elasticConfig.RemoteIndices = remote_indices
+ }
+
if v, ok := userElasticConfig["run_as"]; ok {
definedRuns := v.(*schema.Set)
runs := make([]string, definedRuns.Len())
@@ -380,7 +523,7 @@ func expandKibanaRoleElasticsearch(v interface{}) *kbapi.KibanaRoleElasticsearch
}
}
}
- return elasticConfig
+ return elasticConfig, diags
}
func expandKibanaRoleKibana(v interface{}) ([]kbapi.KibanaRoleKibana, diag.Diagnostics) {
@@ -427,31 +570,53 @@ func expandKibanaRoleKibana(v interface{}) ([]kbapi.KibanaRoleKibana, diag.Diagn
return kibanaConfigs, nil
}
-func flattenKibanaRoleIndicesData(indices *[]kbapi.KibanaRoleElasticsearchIndice) []interface{} {
- if indices != nil {
- oindx := make([]interface{}, len(*indices))
+func flattenKibanaRoleIndicesData(indices []kbapi.KibanaRoleElasticsearchIndice) []interface{} {
+ oindx := make([]interface{}, len(indices))
- for i, index := range *indices {
- oi := make(map[string]interface{})
- oi["names"] = index.Names
- oi["privileges"] = index.Privileges
- oi["query"] = index.Query
+ for i, index := range indices {
+ oi := make(map[string]interface{})
+ oi["names"] = index.Names
+ oi["privileges"] = index.Privileges
+ oi["query"] = index.Query
- if index.FieldSecurity != nil {
- fsec := make(map[string]interface{})
- if grant_v, ok := index.FieldSecurity["grant"]; ok {
- fsec["grant"] = grant_v
- }
- if except_v, ok := index.FieldSecurity["except"]; ok {
- fsec["except"] = except_v
- }
- oi["field_security"] = []interface{}{fsec}
+ if index.FieldSecurity != nil {
+ fsec := make(map[string]interface{})
+ if grant_v, ok := index.FieldSecurity["grant"]; ok {
+ fsec["grant"] = grant_v
}
- oindx[i] = oi
+ if except_v, ok := index.FieldSecurity["except"]; ok {
+ fsec["except"] = except_v
+ }
+ oi["field_security"] = []interface{}{fsec}
}
- return oindx
+ oindx[i] = oi
}
- return make([]interface{}, 0)
+ return oindx
+}
+
+func flattenKibanaRoleRemoteIndicesData(indices []kbapi.KibanaRoleElasticsearchRemoteIndice) []interface{} {
+ oindx := make([]interface{}, len(indices))
+
+ for i, index := range indices {
+ oi := make(map[string]interface{})
+ oi["clusters"] = index.Clusters
+ oi["names"] = index.Names
+ oi["privileges"] = index.Privileges
+ oi["query"] = index.Query
+
+ if index.FieldSecurity != nil {
+ fsec := make(map[string]interface{})
+ if grant_v, ok := index.FieldSecurity["grant"]; ok {
+ fsec["grant"] = grant_v
+ }
+ if except_v, ok := index.FieldSecurity["except"]; ok {
+ fsec["except"] = except_v
+ }
+ oi["field_security"] = []interface{}{fsec}
+ }
+ oindx[i] = oi
+ }
+ return oindx
}
func flattenKibanaRoleElasticsearchData(elastic *kbapi.KibanaRoleElasticsearch) []interface{} {
@@ -460,7 +625,8 @@ func flattenKibanaRoleElasticsearchData(elastic *kbapi.KibanaRoleElasticsearch)
if len(elastic.Cluster) > 0 {
result["cluster"] = elastic.Cluster
}
- result["indices"] = flattenKibanaRoleIndicesData(&elastic.Indices)
+ result["indices"] = flattenKibanaRoleIndicesData(elastic.Indices)
+ result["remote_indices"] = flattenKibanaRoleRemoteIndicesData(elastic.RemoteIndices)
if len(elastic.RunAs) > 0 {
result["run_as"] = elastic.RunAs
}
diff --git a/internal/kibana/role_data_source.go b/internal/kibana/role_data_source.go
index aebd96a527..735f941138 100644
--- a/internal/kibana/role_data_source.go
+++ b/internal/kibana/role_data_source.go
@@ -85,6 +85,72 @@ func DataSourceRole() *schema.Resource {
},
},
},
+ "remote_indices": {
+ Description: "A list of remote indices permissions entries. Remote indices are effective for remote clusters configured with the API key based model. They have no effect for remote clusters configured with the certificate based model.",
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "clusters": {
+ Description: "A list of cluster aliases to which the permissions in this entry apply.",
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "field_security": {
+ Description: "The document fields that the owners of the role have read access to.",
+ Type: schema.TypeList,
+ Optional: true,
+ MaxItems: 1,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "grant": {
+ Description: "List of the fields to grant the access to.",
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "except": {
+ Description: "List of the fields to which the grants will not be applied.",
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ },
+ },
+ },
+ "query": {
+ Description: "A search query that defines the documents the owners of the role have read access to.",
+ Type: schema.TypeString,
+ ValidateFunc: validation.StringIsJSON,
+ DiffSuppressFunc: utils.DiffJsonSuppress,
+ Optional: true,
+ },
+ "names": {
+ Description: "A list of indices (or index name patterns) to which the permissions in this entry apply.",
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "privileges": {
+ Description: "The index level privileges that the owners of the role have on the specified indices.",
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ },
+ },
+ },
"run_as": {
Description: "A list of usernames the owners of this role can impersonate.",
Type: schema.TypeSet,
diff --git a/internal/kibana/role_data_source_test.go b/internal/kibana/role_data_source_test.go
index fb237c2bce..4b33398582 100644
--- a/internal/kibana/role_data_source_test.go
+++ b/internal/kibana/role_data_source_test.go
@@ -5,10 +5,13 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
+ "github.com/elastic/terraform-provider-elasticstack/internal/versionutils"
+ "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)
func TestAccDataSourceKibanaSecurityRole(t *testing.T) {
+ minSupportedRemoteIndicesVersion := version.Must(version.NewSemver("8.10.0"))
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.Providers,
@@ -24,6 +27,22 @@ func TestAccDataSourceKibanaSecurityRole(t *testing.T) {
utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}),
),
},
+ {
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedRemoteIndicesVersion),
+ Config: testAccDataSourceSecurityRoleRemoteIndices,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.elasticstack_kibana_security_role.test", "name", "data_source_test2"),
+ resource.TestCheckNoResourceAttr("data.elasticstack_kibana_security_role.test", "kibana.0.feature.#"),
+ resource.TestCheckNoResourceAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.#"),
+ utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.run_as", []string{"elastic", "kibana"}),
+ utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.base", []string{"all"}),
+ utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}),
+ utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.clusters", []string{"test-cluster"}),
+ utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.field_security.0.grant", []string{"sample"}),
+ utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.names", []string{"sample"}),
+ utils.TestCheckResourceListAttr("data.elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.privileges", []string{"create", "read", "write"}),
+ ),
+ },
},
})
}
@@ -55,3 +74,40 @@ data "elasticstack_kibana_security_role" "test" {
name = elasticstack_kibana_security_role.test.name
}
`
+
+const testAccDataSourceSecurityRoleRemoteIndices = `
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+
+resource "elasticstack_kibana_security_role" "test" {
+ name = "data_source_test2"
+ elasticsearch {
+ cluster = [ "create_snapshot" ]
+ indices {
+ names = ["sample"]
+ privileges = ["create", "read", "write"]
+ }
+ remote_indices {
+ clusters = ["test-cluster"]
+ field_security {
+ grant = ["sample"]
+ except = []
+ }
+ names = ["sample"]
+ privileges = ["create", "read", "write"]
+ }
+ run_as = ["kibana", "elastic"]
+ }
+ kibana {
+ base = [ "all" ]
+ spaces = ["default"]
+ }
+}
+
+data "elasticstack_kibana_security_role" "test" {
+ name = elasticstack_kibana_security_role.test.name
+}
+`
diff --git a/internal/kibana/role_test.go b/internal/kibana/role_test.go
index 55e04ae57b..8c05983f6f 100644
--- a/internal/kibana/role_test.go
+++ b/internal/kibana/role_test.go
@@ -7,6 +7,8 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
+ "github.com/elastic/terraform-provider-elasticstack/internal/versionutils"
+ "github.com/hashicorp/go-version"
sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
@@ -15,6 +17,8 @@ import (
func TestAccResourceKibanaSecurityRole(t *testing.T) {
// generate a random role name
roleName := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
+ roleNameRemoteIndices := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
+ minSupportedRemoteIndicesVersion := version.Must(version.NewSemver("8.10.0"))
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
@@ -44,6 +48,39 @@ func TestAccResourceKibanaSecurityRole(t *testing.T) {
utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}),
),
},
+ {
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedRemoteIndicesVersion),
+ Config: testAccResourceSecurityRoleRemoteIndicesCreate(roleNameRemoteIndices),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_kibana_security_role.test", "name", roleNameRemoteIndices),
+ resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "kibana.0.base.#"),
+ resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.run_as.#"),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.names", []string{"sample"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.0.grant", []string{"sample"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.feature.2.privileges", []string{"minimal_read", "store_search_session", "url_create"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.clusters", []string{"test-cluster"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.field_security.0.grant", []string{"sample"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.names", []string{"sample"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.privileges", []string{"create", "read", "write"}),
+ ),
+ },
+ {
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedRemoteIndicesVersion),
+ Config: testAccResourceSecurityRoleRemoteIndicesUpdate(roleNameRemoteIndices),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_kibana_security_role.test", "name", roleNameRemoteIndices),
+ resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "kibana.0.feature.#"),
+ resource.TestCheckNoResourceAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.indices.0.field_security.#"),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.run_as", []string{"elastic", "kibana"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.base", []string{"all"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "kibana.0.spaces", []string{"default"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.clusters", []string{"test-cluster2"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.field_security.0.grant", []string{"sample2"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.names", []string{"sample2"}),
+ utils.TestCheckResourceListAttr("elasticstack_kibana_security_role.test", "elasticsearch.0.remote_indices.0.privileges", []string{"create", "read", "write"}),
+ ),
+ },
},
})
}
@@ -137,6 +174,113 @@ resource "elasticstack_kibana_security_role" "test" {
`, roleName)
}
+func testAccResourceSecurityRoleRemoteIndicesCreate(roleName string) string {
+ return fmt.Sprintf(`
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+resource "elasticstack_kibana_security_role" "test" {
+ name = "%s"
+ elasticsearch {
+ cluster = [ "create_snapshot" ]
+ indices {
+ field_security {
+ grant = ["sample"]
+ except = []
+ }
+ names = ["sample"]
+ privileges = ["create", "read", "write"]
+ }
+ remote_indices {
+ clusters = ["test-cluster"]
+ field_security {
+ grant = ["sample"]
+ except = []
+ }
+ names = ["sample"]
+ privileges = ["create", "read", "write"]
+ }
+ }
+ kibana {
+ feature {
+ name = "actions"
+ privileges = ["read"]
+ }
+ feature {
+ name = "advancedSettings"
+ privileges = ["read"]
+ }
+ feature {
+ name = "discover"
+ privileges = ["minimal_read", "url_create", "store_search_session"]
+ }
+ feature {
+ name = "generalCases"
+ privileges = ["minimal_read", "cases_delete"]
+ }
+ feature {
+ name = "observabilityCases"
+ privileges = ["minimal_read", "cases_delete"]
+ }
+ feature {
+ name = "osquery"
+ privileges = ["minimal_read", "live_queries_all", "run_saved_queries", "saved_queries_read", "packs_all"]
+ }
+ feature {
+ name = "rulesSettings"
+ privileges = ["minimal_read", "readFlappingSettings"]
+ }
+ feature {
+ name = "securitySolutionCases"
+ privileges = ["minimal_read", "cases_delete"]
+ }
+ feature {
+ name = "visualize"
+ privileges = ["minimal_read", "url_create"]
+ }
+
+ spaces = ["default"]
+ }
+}
+ `, roleName)
+}
+
+func testAccResourceSecurityRoleRemoteIndicesUpdate(roleName string) string {
+ return fmt.Sprintf(`
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+resource "elasticstack_kibana_security_role" "test" {
+ name = "%s"
+ elasticsearch {
+ cluster = [ "create_snapshot" ]
+ indices {
+ names = ["sample"]
+ privileges = ["create", "read", "write"]
+ }
+ remote_indices {
+ clusters = ["test-cluster2"]
+ field_security {
+ grant = ["sample2"]
+ except = []
+ }
+ names = ["sample2"]
+ privileges = ["create", "read", "write"]
+ }
+ run_as = ["kibana", "elastic"]
+ }
+ kibana {
+ base = [ "all" ]
+ spaces = ["default"]
+ }
+}
+ `, roleName)
+}
+
func checkResourceSecurityRoleDestroy(s *terraform.State) error {
client, err := clients.NewAcceptanceTestingClient()
if err != nil {
diff --git a/internal/models/models.go b/internal/models/models.go
index af5cb3f643..9ef5e0b221 100644
--- a/internal/models/models.go
+++ b/internal/models/models.go
@@ -73,13 +73,14 @@ type UserPassword struct {
}
type Role struct {
- Name string `json:"-"`
- Applications []Application `json:"applications,omitempty"`
- Global map[string]interface{} `json:"global,omitempty"`
- Cluster []string `json:"cluster,omitempty"`
- Indices []IndexPerms `json:"indices,omitempty"`
- Metadata map[string]interface{} `json:"metadata,omitempty"`
- RusAs []string `json:"run_as,omitempty"`
+ Name string `json:"-"`
+ Applications []Application `json:"applications,omitempty"`
+ Global map[string]interface{} `json:"global,omitempty"`
+ Cluster []string `json:"cluster,omitempty"`
+ Indices []IndexPerms `json:"indices,omitempty"`
+ RemoteIndices []RemoteIndexPerms `json:"remote_indices,omitempty"`
+ Metadata map[string]interface{} `json:"metadata,omitempty"`
+ RusAs []string `json:"run_as,omitempty"`
}
type ApiKeyRoleDescriptor struct {
@@ -131,6 +132,14 @@ type IndexPerms struct {
AllowRestrictedIndices *bool `json:"allow_restricted_indices,omitempty"`
}
+type RemoteIndexPerms struct {
+ FieldSecurity *FieldSecurity `json:"field_security,omitempty"`
+ Names []string `json:"names"`
+ Clusters []string `json:"clusters"`
+ Privileges []string `json:"privileges"`
+ Query *string `json:"query,omitempty"`
+}
+
type FieldSecurity struct {
Grant []string `json:"grant,omitempty"`
Except []string `json:"except,omitempty"`
diff --git a/libs/go-kibana-rest/kbapi/api.kibana_role_management.go b/libs/go-kibana-rest/kbapi/api.kibana_role_management.go
index 897466ac7f..2ab4195150 100644
--- a/libs/go-kibana-rest/kbapi/api.kibana_role_management.go
+++ b/libs/go-kibana-rest/kbapi/api.kibana_role_management.go
@@ -28,9 +28,10 @@ type KibanaRoleTransientMetadata struct {
// KibanaRoleElasticsearch is the API Elasticsearch object
type KibanaRoleElasticsearch struct {
- Indices []KibanaRoleElasticsearchIndice `json:"indices,omitempty"`
- Cluster []string `json:"cluster,omitempty"`
- RunAs []string `json:"run_as,omitempty"`
+ Indices []KibanaRoleElasticsearchIndice `json:"indices,omitempty"`
+ RemoteIndices []KibanaRoleElasticsearchRemoteIndice `json:"remote_indices,omitempty"`
+ Cluster []string `json:"cluster,omitempty"`
+ RunAs []string `json:"run_as,omitempty"`
}
// KibanaRoleKibana is the API Kibana object
@@ -48,6 +49,15 @@ type KibanaRoleElasticsearchIndice struct {
Query interface{} `json:"query,omitempty"`
}
+// KibanaRoleElasticsearchRemoteIndice is the API remote_indice object
+type KibanaRoleElasticsearchRemoteIndice struct {
+ Clusters []string `json:"clusters,omitempty"`
+ Names []string `json:"names,omitempty"`
+ Privileges []string `json:"privileges,omitempty"`
+ FieldSecurity map[string]interface{} `json:"field_security,omitempty"`
+ Query interface{} `json:"query,omitempty"`
+}
+
// KibanaRoles is a list of role object
type KibanaRoles []KibanaRole
diff --git a/libs/go-kibana-rest/kbapi/api.kibana_role_management_test.go b/libs/go-kibana-rest/kbapi/api.kibana_role_management_test.go
index 81c90b5829..1c1f2e0bca 100644
--- a/libs/go-kibana-rest/kbapi/api.kibana_role_management_test.go
+++ b/libs/go-kibana-rest/kbapi/api.kibana_role_management_test.go
@@ -20,6 +20,19 @@ func (s *KBAPITestSuite) TestKibanaRoleManagement() {
},
},
},
+ RemoteIndices: []KibanaRoleElasticsearchRemoteIndice{
+ {
+ Clusters: []string{
+ "*",
+ },
+ Names: []string{
+ "*",
+ },
+ Privileges: []string{
+ "read",
+ },
+ },
+ },
},
Kibana: []KibanaRoleKibana{
{