Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

opensearch/domain: Allow fine-grained access control to be enabled #26503

Merged
merged 12 commits into from
Aug 29, 2022
Merged
3 changes: 3 additions & 0 deletions .changelog/26503.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_opensearch_domain: Add support for enabling fine-grained access control on existing domains with `advanced_security_options` `anonymous_auth_enabled`
```
4 changes: 2 additions & 2 deletions internal/service/elasticsearch/domain_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestAccElasticsearchDomainDataSource_basic(t *testing.T) {
resourceName := "aws_elasticsearch_domain.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t); testAccPreCheckIAMServiceLinkedRoleEs(t) },
PreCheck: func() { acctest.PreCheck(t); testAccPreCheckIAMServiceLinkedRole(t) },
ErrorCheck: acctest.ErrorCheck(t, elasticsearchservice.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
Steps: []resource.TestStep{
Expand Down Expand Up @@ -63,7 +63,7 @@ func TestAccElasticsearchDomainDataSource_advanced(t *testing.T) {
resourceName := "aws_elasticsearch_domain.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t); testAccPreCheckIAMServiceLinkedRoleEs(t) },
PreCheck: func() { acctest.PreCheck(t); testAccPreCheckIAMServiceLinkedRole(t) },
ErrorCheck: acctest.ErrorCheck(t, elasticsearchservice.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
Steps: []resource.TestStep{
Expand Down
106 changes: 38 additions & 68 deletions internal/service/elasticsearch/domain_test.go

Large diffs are not rendered by default.

21 changes: 15 additions & 6 deletions internal/service/opensearch/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ func ResourceDomain() *schema.Resource {

return !inPlaceEncryptionEnableVersion(d.Get("engine_version").(string))
}),
customdiff.ForceNewIf("advanced_security_options.0.enabled", func(_ context.Context, d *schema.ResourceDiff, meta interface{}) bool {
o, n := d.GetChange("advanced_security_options.0.enabled")
if o.(bool) && !n.(bool) {
return true
}

return false
}),
verify.SetTagsDiff,
),

Expand Down Expand Up @@ -107,10 +115,14 @@ func ResourceDomain() *schema.Resource {
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"anonymous_auth_enabled": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"enabled": {
Type: schema.TypeBool,
Required: true,
ForceNew: true,
},
"internal_user_database_enabled": {
Type: schema.TypeBool,
Expand Down Expand Up @@ -383,6 +395,7 @@ func ResourceDomain() *schema.Resource {
"iops": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
"throughput": {
Type: schema.TypeInt,
Expand Down Expand Up @@ -805,13 +818,9 @@ func resourceDomainRead(d *schema.ResourceData, meta interface{}) error {
// DescribeDomainConfig, if enabled, else use
// values from resource; additionally, append MasterUserOptions
// from resource as they are not returned from the API
if ds.AdvancedSecurityOptions != nil {
if ds.AdvancedSecurityOptions != nil && aws.BoolValue(ds.AdvancedSecurityOptions.Enabled) {
advSecOpts := flattenAdvancedSecurityOptions(ds.AdvancedSecurityOptions)
if !aws.BoolValue(ds.AdvancedSecurityOptions.Enabled) {
advSecOpts[0]["internal_user_database_enabled"] = getUserDBEnabled(d)
}
advSecOpts[0]["master_user_options"] = getMasterUserOptions(d)

if err := d.Set("advanced_security_options", advSecOpts); err != nil {
return fmt.Errorf("error setting advanced_security_options: %w", err)
}
Expand Down
24 changes: 10 additions & 14 deletions internal/service/opensearch/domain_structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ func expandAdvancedSecurityOptions(m []interface{}) *opensearchservice.AdvancedS
config.Enabled = aws.Bool(advancedSecurityEnabled.(bool))

if advancedSecurityEnabled.(bool) {
if v, ok := group["anonymous_auth_enabled"].(bool); ok {
config.AnonymousAuthEnabled = aws.Bool(v)
}

if v, ok := group["internal_user_database_enabled"].(bool); ok {
config.InternalUserDatabaseEnabled = aws.Bool(v)
}
Expand Down Expand Up @@ -173,7 +177,12 @@ func flattenAdvancedSecurityOptions(advancedSecurityOptions *opensearchservice.A

m := map[string]interface{}{}
m["enabled"] = aws.BoolValue(advancedSecurityOptions.Enabled)
if aws.BoolValue(advancedSecurityOptions.Enabled) {

if aws.BoolValue(advancedSecurityOptions.Enabled) && advancedSecurityOptions.AnonymousAuthEnabled != nil {
m["anonymous_auth_enabled"] = aws.BoolValue(advancedSecurityOptions.AnonymousAuthEnabled)
}

if aws.BoolValue(advancedSecurityOptions.Enabled) && advancedSecurityOptions.InternalUserDatabaseEnabled != nil {
m["internal_user_database_enabled"] = aws.BoolValue(advancedSecurityOptions.InternalUserDatabaseEnabled)
}

Expand Down Expand Up @@ -279,19 +288,6 @@ func getMasterUserOptions(d *schema.ResourceData) []interface{} {
return []interface{}{}
}

func getUserDBEnabled(d *schema.ResourceData) bool {
if v, ok := d.GetOk("advanced_security_options"); ok {
options := v.([]interface{})
if len(options) > 0 && options[0] != nil {
m := options[0].(map[string]interface{})
if enabled, ok := m["internal_user_database_enabled"]; ok {
return enabled.(bool)
}
}
}
return false
}

func expandLogPublishingOptions(m *schema.Set) map[string]*opensearchservice.LogPublishingOption {
options := make(map[string]*opensearchservice.LogPublishingOption)

Expand Down
138 changes: 101 additions & 37 deletions internal/service/opensearch/domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ package opensearch_test
import (
"fmt"
"regexp"
"strings"
"testing"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
"github.com/aws/aws-sdk-go/service/elb"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/opensearchservice"
sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
Expand Down Expand Up @@ -715,7 +713,51 @@ func TestAccOpenSearchDomain_AdvancedSecurityOptions_userDB(t *testing.T) {
Config: testAccDomainConfig_advancedSecurityOptionsUserDB(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckDomainExists(resourceName, &domain),
testAccCheckAdvancedSecurityOptions(true, true, &domain),
testAccCheckAdvancedSecurityOptions(true, true, false, &domain),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateId: rName,
ImportStateVerify: true,
// MasterUserOptions are not returned from DescribeDomainConfig
ImportStateVerifyIgnore: []string{
"advanced_security_options.0.internal_user_database_enabled",
"advanced_security_options.0.master_user_options",
},
},
},
})
}

func TestAccOpenSearchDomain_AdvancedSecurityOptions_anonymousAuth(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

var domain opensearchservice.DomainStatus
rName := testAccRandomDomainName()
resourceName := "aws_opensearch_domain.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t); testAccPreCheckIAMServiceLinkedRole(t) },
ErrorCheck: acctest.ErrorCheck(t, opensearchservice.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckDomainDestroy,
Steps: []resource.TestStep{
{
Config: testAccDomainConfig_advancedSecurityOptionsAnonymousAuth(rName, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckDomainExists(resourceName, &domain),
testAccCheckAdvancedSecurityOptions(false, true, true, &domain),
),
},
{
Config: testAccDomainConfig_advancedSecurityOptionsAnonymousAuth(rName, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckDomainExists(resourceName, &domain),
testAccCheckAdvancedSecurityOptions(true, true, true, &domain),
),
},
{
Expand Down Expand Up @@ -752,7 +794,7 @@ func TestAccOpenSearchDomain_AdvancedSecurityOptions_iam(t *testing.T) {
Config: testAccDomainConfig_advancedSecurityOptionsIAM(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckDomainExists(resourceName, &domain),
testAccCheckAdvancedSecurityOptions(true, false, &domain),
testAccCheckAdvancedSecurityOptions(true, false, false, &domain),
),
},
{
Expand Down Expand Up @@ -789,7 +831,7 @@ func TestAccOpenSearchDomain_AdvancedSecurityOptions_disabled(t *testing.T) {
Config: testAccDomainConfig_advancedSecurityOptionsDisabled(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckDomainExists(resourceName, &domain),
testAccCheckAdvancedSecurityOptions(false, false, &domain),
testAccCheckAdvancedSecurityOptions(false, false, false, &domain),
),
},
{
Expand All @@ -799,8 +841,7 @@ func TestAccOpenSearchDomain_AdvancedSecurityOptions_disabled(t *testing.T) {
ImportStateVerify: true,
// MasterUserOptions are not returned from DescribeDomainConfig
ImportStateVerifyIgnore: []string{
"advanced_security_options.0.internal_user_database_enabled",
"advanced_security_options.0.master_user_options",
"advanced_security_options",
},
},
},
Expand Down Expand Up @@ -1684,7 +1725,7 @@ func testAccCheckNodeToNodeEncrypted(encrypted bool, status *opensearchservice.D
}
}

func testAccCheckAdvancedSecurityOptions(enabled bool, userDbEnabled bool, status *opensearchservice.DomainStatus) resource.TestCheckFunc {
func testAccCheckAdvancedSecurityOptions(enabled bool, userDbEnabled bool, anonymousAuthEnabled bool, status *opensearchservice.DomainStatus) resource.TestCheckFunc {
return func(s *terraform.State) error {
conf := status.AdvancedSecurityOptions

Expand All @@ -1706,6 +1747,16 @@ func testAccCheckAdvancedSecurityOptions(enabled bool, userDbEnabled bool, statu
}
}

if aws.BoolValue(conf.Enabled) {
if aws.BoolValue(conf.AnonymousAuthEnabled) != anonymousAuthEnabled {
return fmt.Errorf(
"AdvancedSecurityOptions.AnonymousAuthEnabled not set properly. Given: %t, Expected: %t",
aws.BoolValue(conf.AnonymousAuthEnabled),
anonymousAuthEnabled,
)
}
}

return nil
}
}
Expand Down Expand Up @@ -1815,35 +1866,7 @@ func testAccGetValidStartAtTime(t *testing.T, timeUntilStart string) string {
}

func testAccPreCheckIAMServiceLinkedRole(t *testing.T) {
conn := acctest.Provider.Meta().(*conns.AWSClient).IAMConn
dnsSuffix := acctest.Provider.Meta().(*conns.AWSClient).DNSSuffix

input := &iam.ListRolesInput{
PathPrefix: aws.String("/aws-service-role/opensearchservice."),
}

var role *iam.Role
err := conn.ListRolesPages(input, func(page *iam.ListRolesOutput, lastPage bool) bool {
for _, r := range page.Roles {
if strings.HasPrefix(aws.StringValue(r.Path), "/aws-service-role/opensearchservice.") {
role = r
}
}

return !lastPage
})

if acctest.PreCheckSkipError(err) {
t.Skipf("skipping acceptance testing: %s", err)
}

if err != nil {
t.Fatalf("unexpected PreCheck error: %s", err)
}

if role == nil {
t.Fatalf("missing IAM Service Linked Role (opensearchservice.%s), please create it in the AWS account and retry", dnsSuffix)
}
acctest.PreCheckIAMServiceLinkedRole(t, "/aws-service-role/opensearchservice")
}

func testAccPreCheckCognitoIdentityProvider(t *testing.T) {
Expand Down Expand Up @@ -2887,6 +2910,47 @@ resource "aws_opensearch_domain" "test" {
`, rName)
}

func testAccDomainConfig_advancedSecurityOptionsAnonymousAuth(rName string, enabled bool) string {
return fmt.Sprintf(`
resource "aws_opensearch_domain" "test" {
domain_name = %[1]q
engine_version = "Elasticsearch_7.1"

cluster_config {
instance_type = "r5.large.search"
}

advanced_security_options {
enabled = %[2]t
anonymous_auth_enabled = true
internal_user_database_enabled = true
master_user_options {
master_user_name = "testmasteruser"
master_user_password = "Barbarbarbar1!"
}
}

encrypt_at_rest {
enabled = true
}

domain_endpoint_options {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}

node_to_node_encryption {
enabled = true
}

ebs_options {
ebs_enabled = true
volume_size = 10
}
}
`, rName, enabled)
}

func testAccDomainConfig_advancedSecurityOptionsIAM(rName string) string {
return fmt.Sprintf(`
resource "aws_iam_user" "test" {
Expand Down
Loading