Skip to content

Commit

Permalink
Merge pull request #2632 from tomelliff/es-encrypt-at-rest
Browse files Browse the repository at this point in the history
resource/aws_elasticsearch_domain: Add support for encrypt_at_rest
  • Loading branch information
radeksimko authored Jan 22, 2018
2 parents 51a6623 + a13f2a9 commit 924b18d
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 0 deletions.
44 changes: 44 additions & 0 deletions aws/resource_aws_elasticsearch_domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log"
"regexp"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand Down Expand Up @@ -94,6 +95,28 @@ func resourceAwsElasticSearchDomain() *schema.Resource {
},
},
},
"encrypt_at_rest": {
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Required: true,
ForceNew: true,
},
"kms_key_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
DiffSuppressFunc: suppressEquivalentKmsKeyIds,
},
},
},
},
"cluster_config": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -296,6 +319,16 @@ func resourceAwsElasticSearchDomainCreate(d *schema.ResourceData, meta interface
}
}

if v, ok := d.GetOk("encrypt_at_rest"); ok {
options := v.([]interface{})
if options[0] == nil {
return fmt.Errorf("At least one field is expected inside encrypt_at_rest")
}

s := options[0].(map[string]interface{})
input.EncryptionAtRestOptions = expandESEncryptAtRestOptions(s)
}

if v, ok := d.GetOk("cluster_config"); ok {
config := v.([]interface{})

Expand Down Expand Up @@ -466,6 +499,10 @@ func resourceAwsElasticSearchDomainRead(d *schema.ResourceData, meta interface{}
if err != nil {
return err
}
err = d.Set("encrypt_at_rest", flattenESEncryptAtRestOptions(ds.EncryptionAtRestOptions))
if err != nil {
return err
}
err = d.Set("cluster_config", flattenESClusterConfig(ds.ElasticsearchClusterConfig))
if err != nil {
return err
Expand Down Expand Up @@ -684,6 +721,13 @@ func resourceAwsElasticSearchDomainDelete(d *schema.ResourceData, meta interface
return err
}

func suppressEquivalentKmsKeyIds(k, old, new string, d *schema.ResourceData) bool {
// The Elasticsearch API accepts a short KMS key id but always returns the ARN of the key.
// The ARN is of the format 'arn:aws:kms:REGION:ACCOUNT_ID:key/KMS_KEY_ID'.
// These should be treated as equivalent.
return strings.Contains(old, new)
}

func getKibanaEndpoint(d *schema.ResourceData) string {
return d.Get("endpoint").(string) + "/_plugin/kibana/"
}
104 changes: 104 additions & 0 deletions aws/resource_aws_elasticsearch_domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,44 @@ func TestAccAWSElasticSearchDomain_policy(t *testing.T) {
})
}

func TestAccAWSElasticSearchDomain_encrypt_at_rest_default_key(t *testing.T) {
var domain elasticsearch.ElasticsearchDomainStatus

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckESDomainDestroy,
Steps: []resource.TestStep{
{
Config: testAccESDomainConfigWithEncryptAtRestDefaultKey(acctest.RandInt()),
Check: resource.ComposeTestCheckFunc(
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
testAccCheckESEncrypted(true, &domain),
),
},
},
})
}

func TestAccAWSElasticSearchDomain_encrypt_at_rest_specify_key(t *testing.T) {
var domain elasticsearch.ElasticsearchDomainStatus

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckESDomainDestroy,
Steps: []resource.TestStep{
{
Config: testAccESDomainConfigWithEncryptAtRestWithKey(acctest.RandInt()),
Check: resource.ComposeTestCheckFunc(
testAccCheckESDomainExists("aws_elasticsearch_domain.example", &domain),
testAccCheckESEncrypted(true, &domain),
),
},
},
})
}

func TestAccAWSElasticSearchDomain_tags(t *testing.T) {
var domain elasticsearch.ElasticsearchDomainStatus
var td elasticsearch.ListTagsOutput
Expand Down Expand Up @@ -340,6 +378,16 @@ func testAccCheckESNumberOfInstances(numberOfInstances int, status *elasticsearc
}
}

func testAccCheckESEncrypted(encrypted bool, status *elasticsearch.ElasticsearchDomainStatus) resource.TestCheckFunc {
return func(s *terraform.State) error {
conf := status.EncryptionAtRestOptions
if *conf.Enabled != encrypted {
return fmt.Errorf("Encrypt at rest not set properly. Given: %t, Expected: %t", *conf.Enabled, encrypted)
}
return nil
}
}

func testAccLoadESTags(conf *elasticsearch.ElasticsearchDomainStatus, td *elasticsearch.ListTagsOutput) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).esconn
Expand Down Expand Up @@ -412,6 +460,7 @@ func testAccESDomainConfig(randInt int) string {
return fmt.Sprintf(`
resource "aws_elasticsearch_domain" "example" {
domain_name = "tf-test-%d"
ebs_options {
ebs_enabled = true
volume_size = 10
Expand Down Expand Up @@ -505,6 +554,61 @@ data "aws_iam_policy_document" "instance-assume-role-policy" {
`, randESId, randRoleId)
}

func testAccESDomainConfigWithEncryptAtRestDefaultKey(randESId int) string {
return fmt.Sprintf(`
resource "aws_elasticsearch_domain" "example" {
domain_name = "tf-test-%d"
elasticsearch_version = "6.0"
# Encrypt at rest requires m4/c4/r4/i2 instances. See http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-supported-instance-types.html
cluster_config {
instance_type = "m4.large.elasticsearch"
}
ebs_options {
ebs_enabled = true
volume_size = 10
}
encrypt_at_rest {
enabled = true
}
}
`, randESId)
}

func testAccESDomainConfigWithEncryptAtRestWithKey(randESId int) string {
return fmt.Sprintf(`
resource "aws_kms_key" "es" {
description = "kms-key-for-tf-test-%d"
deletion_window_in_days = 7
}
resource "aws_elasticsearch_domain" "example" {
domain_name = "tf-test-%d"
elasticsearch_version = "6.0"
# Encrypt at rest requires m4/c4/r4/i2 instances. See http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-supported-instance-types.html
cluster_config {
instance_type = "m4.large.elasticsearch"
}
ebs_options {
ebs_enabled = true
volume_size = 10
}
encrypt_at_rest {
enabled = true
kms_key_id = "${aws_kms_key.es.key_id}"
}
}
`, randESId, randESId)
}

func testAccESDomainConfig_complex(randInt int) string {
return fmt.Sprintf(`
resource "aws_elasticsearch_domain" "example" {
Expand Down
30 changes: 30 additions & 0 deletions aws/structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,36 @@ func expandESEBSOptions(m map[string]interface{}) *elasticsearch.EBSOptions {
return &options
}

func flattenESEncryptAtRestOptions(o *elasticsearch.EncryptionAtRestOptions) []map[string]interface{} {
if o == nil {
return []map[string]interface{}{}
}

m := map[string]interface{}{}

if o.Enabled != nil {
m["enabled"] = *o.Enabled
}
if o.KmsKeyId != nil {
m["kms_key_id"] = *o.KmsKeyId
}

return []map[string]interface{}{m}
}

func expandESEncryptAtRestOptions(m map[string]interface{}) *elasticsearch.EncryptionAtRestOptions {
options := elasticsearch.EncryptionAtRestOptions{}

if v, ok := m["enabled"]; ok {
options.Enabled = aws.Bool(v.(bool))
}
if v, ok := m["kms_key_id"]; ok && v.(string) != "" {
options.KmsKeyId = aws.String(v.(string))
}

return &options
}

func flattenESVPCDerivedInfo(o *elasticsearch.VPCDerivedInfo) []map[string]interface{} {
m := map[string]interface{}{}

Expand Down
6 changes: 6 additions & 0 deletions website/docs/r/elasticsearch_domain.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ The following arguments are supported:
* `access_policies` - (Optional) IAM policy document specifying the access policies for the domain
* `advanced_options` - (Optional) Key-value string pairs to specify advanced configuration options.
* `ebs_options` - (Optional) EBS related options, may be required based on chosen [instance size](https://aws.amazon.com/elasticsearch-service/pricing/). See below.
* `encrypt_at_rest` - (Optional) Encrypt at rest options. Only available for [certain instance types](http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-supported-instance-types.html). See below.
* `cluster_config` - (Optional) Cluster configuration of the domain, see below.
* `snapshot_options` - (Optional) Snapshot related options, see below.
* `vpc_options` - (Optional) VPC related options, see below. Adding or removing this configuration forces a new resource ([documentation](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-vpc.html#es-vpc-limitations)).
Expand All @@ -73,6 +74,11 @@ The following arguments are supported:
* `iops` - (Optional) The baseline input/output (I/O) performance of EBS volumes
attached to data nodes. Applicable only for the Provisioned IOPS EBS volume type.

**encrypt_at_rest** supports the following attributes:

* `enabled` - (Required) Whether to enable encryption at rest. If the `encrypt_at_rest` block is not provided then this defaults to `false`.
* `kms_key_id` - (Optional) The KMS key id to encrypt the Elasticsearch domain with. If not specified then it defaults to using the `aws/es` service KMS key.

**cluster_config** supports the following attributes:

* `instance_type` - (Optional) Instance type of data nodes in the cluster.
Expand Down

0 comments on commit 924b18d

Please sign in to comment.