diff --git a/aws/resource_aws_elasticsearch_domain.go b/aws/resource_aws_elasticsearch_domain.go index 83c38f2bde5..ce517b3395a 100644 --- a/aws/resource_aws_elasticsearch_domain.go +++ b/aws/resource_aws_elasticsearch_domain.go @@ -140,7 +140,8 @@ func resourceAwsElasticSearchDomain() *schema.Resource { Schema: map[string]*schema.Schema{ "enforce_https": { Type: schema.TypeBool, - Required: true, + Optional: true, + Default: true, }, "tls_security_policy": { Type: schema.TypeString, @@ -151,6 +152,22 @@ func resourceAwsElasticSearchDomain() *schema.Resource { elasticsearch.TLSSecurityPolicyPolicyMinTls12201907, }, false), }, + "custom_endpoint_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "custom_endpoint": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: isCustomEndpointDisabled, + }, + "custom_endpoint_certificate_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateArn, + DiffSuppressFunc: isCustomEndpointDisabled, + }, }, }, }, @@ -1037,6 +1054,15 @@ func isDedicatedMasterDisabled(k, old, new string, d *schema.ResourceData) bool return false } +func isCustomEndpointDisabled(k, old, new string, d *schema.ResourceData) bool { + v, ok := d.GetOk("domain_endpoint_options") + if ok { + domainEndpointOptions := v.([]interface{})[0].(map[string]interface{}) + return !domainEndpointOptions["custom_endpoint_enabled"].(bool) + } + return false +} + func expandESNodeToNodeEncryptionOptions(s map[string]interface{}) *elasticsearch.NodeToNodeEncryptionOptions { options := elasticsearch.NodeToNodeEncryptionOptions{} diff --git a/aws/resource_aws_elasticsearch_domain_test.go b/aws/resource_aws_elasticsearch_domain_test.go index bf1b4d378b8..54de07f70fd 100644 --- a/aws/resource_aws_elasticsearch_domain_test.go +++ b/aws/resource_aws_elasticsearch_domain_test.go @@ -122,6 +122,58 @@ func TestAccAWSElasticSearchDomain_RequireHTTPS(t *testing.T) { }) } +func TestAccAWSElasticSearchDomain_CustomEndpoint(t *testing.T) { + var domain elasticsearch.ElasticsearchDomainStatus + ri := acctest.RandInt() + resourceId := fmt.Sprintf("tf-test-%d", ri) + resourceName := "aws_elasticsearch_domain.example" + customEndpoint := fmt.Sprintf("%s.example.com", resourceId) + certResourceName := "aws_acm_certificate.example" + certKey := tlsRsaPrivateKeyPem(2048) + certificate := tlsRsaX509SelfSignedCertificatePem(certKey, customEndpoint) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, elasticsearch.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckESDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccESDomainConfig_CustomEndpoint(ri, true, "Policy-Min-TLS-1-0-2019-07", true, customEndpoint, certKey, certificate), + Check: resource.ComposeTestCheckFunc( + testAccCheckESDomainExists(resourceName, &domain), + resource.TestCheckResourceAttr(resourceName, "domain_endpoint_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "domain_endpoint_options.0.custom_endpoint_enabled", "true"), + resource.TestCheckResourceAttrSet(resourceName, "domain_endpoint_options.0.custom_endpoint"), + resource.TestCheckResourceAttrPair(resourceName, "domain_endpoint_options.0.custom_endpoint_certificate_arn", certResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateId: resourceId, + ImportStateVerify: true, + }, + { + Config: testAccESDomainConfig_CustomEndpoint(ri, true, "Policy-Min-TLS-1-0-2019-07", true, customEndpoint, certKey, certificate), + Check: resource.ComposeTestCheckFunc( + testAccCheckESDomainExists(resourceName, &domain), + testAccCheckESDomainEndpointOptions(true, "Policy-Min-TLS-1-0-2019-07", &domain), + testAccCheckESCustomEndpoint(resourceName, true, customEndpoint, &domain), + ), + }, + { + Config: testAccESDomainConfig_CustomEndpoint(ri, true, "Policy-Min-TLS-1-0-2019-07", false, customEndpoint, certKey, certificate), + Check: resource.ComposeTestCheckFunc( + testAccCheckESDomainExists(resourceName, &domain), + testAccCheckESDomainEndpointOptions(true, "Policy-Min-TLS-1-0-2019-07", &domain), + testAccCheckESCustomEndpoint(resourceName, false, customEndpoint, &domain), + ), + }, + }, + }) +} + func TestAccAWSElasticSearchDomain_ClusterConfig_ZoneAwarenessConfig(t *testing.T) { var domain1, domain2, domain3, domain4 elasticsearch.ElasticsearchDomainStatus rName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(16, acctest.CharSetAlphaNum)) // len = 28 @@ -1092,6 +1144,29 @@ func testAccCheckESDomainEndpointOptions(enforceHTTPS bool, tls string, status * } } +func testAccCheckESCustomEndpoint(n string, customEndpointEnabled bool, customEndpoint string, status *elasticsearch.ElasticsearchDomainStatus) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + options := status.DomainEndpointOptions + if *options.CustomEndpointEnabled != customEndpointEnabled { + return fmt.Errorf("CustomEndpointEnabled differ. Given: %t, Expected: %t", *options.CustomEndpointEnabled, customEndpointEnabled) + } + if *options.CustomEndpointEnabled { + if *options.CustomEndpoint != customEndpoint { + return fmt.Errorf("CustomEndpoint differ. Given: %s, Expected: %s", *options.CustomEndpoint, customEndpoint) + } + customEndpointCertificateArn := rs.Primary.Attributes["domain_endpoint_options.0.custom_endpoint_certificate_arn"] + if *options.CustomEndpointCertificateArn != customEndpointCertificateArn { + return fmt.Errorf("CustomEndpointCertificateArn differ. Given: %s, Expected: %s", *options.CustomEndpointCertificateArn, customEndpointCertificateArn) + } + } + return nil + } +} + func testAccCheckESNumberOfSecurityGroups(numberOfSecurityGroups int, status *elasticsearch.ElasticsearchDomainStatus) resource.TestCheckFunc { return func(s *terraform.State) error { count := len(status.VPCOptions.SecurityGroupIds) @@ -1355,6 +1430,32 @@ resource "aws_elasticsearch_domain" "example" { `, randInt, enforceHttps, tlsSecurityPolicy) } +func testAccESDomainConfig_CustomEndpoint(randInt int, enforceHttps bool, tlsSecurityPolicy string, customEndpointEnabled bool, customEndpoint string, certKey string, certBody string) string { + return fmt.Sprintf(` +resource "aws_acm_certificate" "example" { + private_key = "%[6]s" + certificate_body = "%[7]s" +} + +resource "aws_elasticsearch_domain" "example" { + domain_name = "tf-test-%[1]d" + + domain_endpoint_options { + enforce_https = %[2]t + tls_security_policy = %[3]q + custom_endpoint_enabled = %[4]t + custom_endpoint = "%[5]s" + custom_endpoint_certificate_arn = aws_acm_certificate.example.arn + } + + ebs_options { + ebs_enabled = true + volume_size = 10 + } +} +`, randInt, enforceHttps, tlsSecurityPolicy, customEndpointEnabled, customEndpoint, tlsPemEscapeNewlines(certKey), tlsPemEscapeNewlines(certBody)) +} + func testAccESDomainConfig_ClusterConfig_ZoneAwarenessConfig_AvailabilityZoneCount(rName string, availabilityZoneCount int) string { return fmt.Sprintf(` resource "aws_elasticsearch_domain" "test" { diff --git a/aws/structure.go b/aws/structure.go index 20acfb8adb0..86f15857e0e 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -1376,6 +1376,20 @@ func expandESDomainEndpointOptions(l []interface{}) *elasticsearch.DomainEndpoin domainEndpointOptions.TLSSecurityPolicy = aws.String(v) } + if customEndpointEnabled, ok := m["custom_endpoint_enabled"]; ok { + domainEndpointOptions.CustomEndpointEnabled = aws.Bool(customEndpointEnabled.(bool)) + + if customEndpointEnabled.(bool) { + if v, ok := m["custom_endpoint"].(string); ok && v != "" { + domainEndpointOptions.CustomEndpoint = aws.String(v) + } + + if v, ok := m["custom_endpoint_certificate_arn"].(string); ok && v != "" { + domainEndpointOptions.CustomEndpointCertificateArn = aws.String(v) + } + } + } + return domainEndpointOptions } @@ -1385,8 +1399,17 @@ func flattenESDomainEndpointOptions(domainEndpointOptions *elasticsearch.DomainE } m := map[string]interface{}{ - "enforce_https": aws.BoolValue(domainEndpointOptions.EnforceHTTPS), - "tls_security_policy": aws.StringValue(domainEndpointOptions.TLSSecurityPolicy), + "enforce_https": aws.BoolValue(domainEndpointOptions.EnforceHTTPS), + "tls_security_policy": aws.StringValue(domainEndpointOptions.TLSSecurityPolicy), + "custom_endpoint_enabled": aws.BoolValue(domainEndpointOptions.CustomEndpointEnabled), + } + if aws.BoolValue(domainEndpointOptions.CustomEndpointEnabled) { + if domainEndpointOptions.CustomEndpoint != nil { + m["custom_endpoint"] = aws.StringValue(domainEndpointOptions.CustomEndpoint) + } + if domainEndpointOptions.CustomEndpointCertificateArn != nil { + m["custom_endpoint_certificate_arn"] = aws.StringValue(domainEndpointOptions.CustomEndpointCertificateArn) + } } return []interface{}{m} diff --git a/website/docs/r/elasticsearch_domain.html.markdown b/website/docs/r/elasticsearch_domain.html.markdown index 1c93bb85a90..fa8e69cea72 100644 --- a/website/docs/r/elasticsearch_domain.html.markdown +++ b/website/docs/r/elasticsearch_domain.html.markdown @@ -252,8 +252,11 @@ The **advanced_security_options** block supports the following attributes: **domain_endpoint_options** supports the following attributes: -* `enforce_https` - (Required) Whether or not to require HTTPS +* `enforce_https` - (Optional) Whether or not to require HTTPS. Defaults to `true`. * `tls_security_policy` - (Optional) The name of the TLS security policy that needs to be applied to the HTTPS endpoint. Valid values: `Policy-Min-TLS-1-0-2019-07` and `Policy-Min-TLS-1-2-2019-07`. Terraform will only perform drift detection if a configuration value is provided. +* `custom_endpoint_enabled` - (Optional) Whether to enable custom endpoint for the Elasticsearch domain +* `custom_endpoint` - (Optional) Fully qualified domain for your custom endpoint +* `custom_endpoint_certificate_arn` - (Optional) ACM certificate ARN for your custom endpoint **cluster_config** supports the following attributes: