diff --git a/internal/services/redis/redis_cache_data_source.go b/internal/services/redis/redis_cache_data_source.go index 792be98bb762..a584dec28f45 100644 --- a/internal/services/redis/redis_cache_data_source.go +++ b/internal/services/redis/redis_cache_data_source.go @@ -235,6 +235,11 @@ func dataSourceRedisCache() *pluginsdk.Resource { Sensitive: true, }, + "access_keys_authentication_disabled": { + Type: pluginsdk.TypeBool, + Computed: true, + }, + "tags": commonschema.TagsDataSource(), }, } @@ -344,6 +349,8 @@ func dataSourceRedisCacheRead(d *pluginsdk.ResourceData, meta interface{}) error d.Set("primary_connection_string", getRedisConnectionString(*props.HostName, *props.SslPort, *keys.Model.PrimaryKey, enableSslPort)) d.Set("secondary_connection_string", getRedisConnectionString(*props.HostName, *props.SslPort, *keys.Model.SecondaryKey, enableSslPort)) + d.Set("access_keys_authentication_disabled", *props.DisableAccessKeyAuthentication) + if err := tags.FlattenAndSet(d, model.Tags); err != nil { return fmt.Errorf("setting `tags`: %+v", err) } diff --git a/internal/services/redis/redis_cache_data_source_test.go b/internal/services/redis/redis_cache_data_source_test.go index ff753f9a1096..154e5a570821 100644 --- a/internal/services/redis/redis_cache_data_source_test.go +++ b/internal/services/redis/redis_cache_data_source_test.go @@ -30,6 +30,7 @@ func TestAccRedisCacheDataSource_standard(t *testing.T) { check.That(data.ResourceName).Key("tags.environment").HasValue("production"), check.That(data.ResourceName).Key("primary_connection_string").Exists(), check.That(data.ResourceName).Key("secondary_connection_string").Exists(), + check.That(data.ResourceName).Key("access_keys_authentication_disabled").Exists(), ), }, }) diff --git a/internal/services/redis/redis_cache_resource.go b/internal/services/redis/redis_cache_resource.go index d2072b15db07..9be7ed289da8 100644 --- a/internal/services/redis/redis_cache_resource.go +++ b/internal/services/redis/redis_cache_resource.go @@ -365,6 +365,11 @@ func resourceRedisCache() *pluginsdk.Resource { }, }, + "access_keys_authentication_disabled": { + Type: pluginsdk.TypeBool, + Optional: true, + }, + "tags": commonschema.Tags(), }, @@ -376,6 +381,12 @@ func resourceRedisCache() *pluginsdk.Resource { } return false }), + pluginsdk.CustomizeDiffShim(func(ctx context.Context, diff *pluginsdk.ResourceDiff, v interface{}) error { + return validate.ValidateAccessKeysAuth( + diff.Get("access_keys_authentication_disabled").(bool), + diff.Get("redis_configuration.0.active_directory_authentication_enabled").(bool), + ) + }), ), } @@ -499,7 +510,8 @@ func resourceRedisCacheCreate(d *pluginsdk.ResourceData, meta interface{}) error parameters := redis.RedisCreateParameters{ Location: location.Normalize(d.Get("location").(string)), Properties: redis.RedisCreateProperties{ - EnableNonSslPort: pointer.To(enableNonSslPort.(bool)), + DisableAccessKeyAuthentication: pointer.To(d.Get("access_keys_authentication_disabled").(bool)), + EnableNonSslPort: pointer.To(enableNonSslPort.(bool)), Sku: redis.Sku{ Capacity: int64(d.Get("capacity").(int)), Family: redis.SkuFamily(d.Get("family").(string)), @@ -613,8 +625,9 @@ func resourceRedisCacheUpdate(d *pluginsdk.ResourceData, meta interface{}) error parameters := redis.RedisUpdateParameters{ Properties: &redis.RedisUpdateProperties{ - MinimumTlsVersion: pointer.To(redis.TlsVersion(d.Get("minimum_tls_version").(string))), - EnableNonSslPort: pointer.To(enableNonSslPort.(bool)), + DisableAccessKeyAuthentication: pointer.To(d.Get("access_keys_authentication_disabled").(bool)), + MinimumTlsVersion: pointer.To(redis.TlsVersion(d.Get("minimum_tls_version").(string))), + EnableNonSslPort: pointer.To(enableNonSslPort.(bool)), Sku: &redis.Sku{ Capacity: int64(d.Get("capacity").(int)), Family: redis.SkuFamily(d.Get("family").(string)), @@ -837,6 +850,8 @@ func resourceRedisCacheRead(d *pluginsdk.ResourceData, meta interface{}) error { d.Set("primary_access_key", keysResp.Model.PrimaryKey) d.Set("secondary_access_key", keysResp.Model.SecondaryKey) + d.Set("access_keys_authentication_disabled", props.DisableAccessKeyAuthentication) + if err := tags.FlattenAndSet(d, model.Tags); err != nil { return fmt.Errorf("setting `tags`: %+v", err) } diff --git a/internal/services/redis/redis_cache_resource_test.go b/internal/services/redis/redis_cache_resource_test.go index a3d458980bdf..1ce24148f076 100644 --- a/internal/services/redis/redis_cache_resource_test.go +++ b/internal/services/redis/redis_cache_resource_test.go @@ -561,6 +561,35 @@ func TestAccRedisCache_SkuDowngrade(t *testing.T) { }) } +func TestAccRedisCache_AccessKeysAuthenticationEnabledDisabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_redis_cache", "test") + r := RedisCacheResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.accessKeysAuthentication(data, false, false), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.accessKeysAuthentication(data, true, true), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.accessKeysAuthentication(data, false, false), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t RedisCacheResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := redis.ParseRediID(state.ID) if err != nil { @@ -1594,3 +1623,31 @@ resource "azurerm_redis_cache" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } + +func (RedisCacheResource) accessKeysAuthentication(data acceptance.TestData, accessKeysAuthenticationDisabled bool, activeDirectoryAuthenticationEnabled bool) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_redis_cache" "test" { + name = "acctestRedis-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + capacity = 1 + family = "C" + sku_name = "Basic" + non_ssl_port_enabled = false + minimum_tls_version = "1.2" + access_keys_authentication_disabled = %t + + redis_configuration { + active_directory_authentication_enabled = %t + } +}`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, accessKeysAuthenticationDisabled, accessKeysAuthenticationDisabled) +} diff --git a/internal/services/redis/validate/access_keys_auth.go b/internal/services/redis/validate/access_keys_auth.go new file mode 100644 index 000000000000..496f82a687c9 --- /dev/null +++ b/internal/services/redis/validate/access_keys_auth.go @@ -0,0 +1,18 @@ +package validate + +import ( + "fmt" + "log" +) + +// Entra (AD) auth has to be set to disable access keys auth +// https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication +func ValidateAccessKeysAuth(accessKeysAuthenticationDisabled bool, activeDirectoryAuthenticationEnabled bool) error { + log.Printf("[DEBUG] ValidateAccessKeysAuth: accessKeysAuthenticationDisabled: %v, activeDirectoryAuthenticationEnabled: %v", accessKeysAuthenticationDisabled, activeDirectoryAuthenticationEnabled) + + if accessKeysAuthenticationDisabled && !activeDirectoryAuthenticationEnabled { + return fmt.Errorf("microsoft entra authorization (active_directory_authentication_enabled) must be enabled in order to disable access key authentication (access_keys_authentication_disabled): https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication#disable-access-key-authentication-on-your-cache") + } + + return nil +} diff --git a/internal/services/redis/validate/access_keys_auth_test.go b/internal/services/redis/validate/access_keys_auth_test.go new file mode 100644 index 000000000000..0f1f5962933c --- /dev/null +++ b/internal/services/redis/validate/access_keys_auth_test.go @@ -0,0 +1,17 @@ +package validate + +import ( + "testing" +) + +func TestValidateAccessKeysAuth_valid(t *testing.T) { + if err := ValidateAccessKeysAuth(true, true); err != nil { + t.Fatalf("Should be valid if accessKeysAuthenticationDisabled: true and activeDirectoryAuthenticationEnabled: true but got error: %v", err) + } +} + +func TestValidateAccessKeysAuth_invalid(t *testing.T) { + if err := ValidateAccessKeysAuth(true, false); err == nil { + t.Fatalf("Should return error if accessKeysAuthenticationDisabled: true and activeDirectoryAuthenticationEnabled: false") + } +} diff --git a/website/docs/d/redis_cache.html.markdown b/website/docs/d/redis_cache.html.markdown index 511a4ef99933..8b5ee43d9716 100644 --- a/website/docs/d/redis_cache.html.markdown +++ b/website/docs/d/redis_cache.html.markdown @@ -68,6 +68,8 @@ output "hostname" { * `secondary_connection_string` - The secondary connection string of the Redis Instance. +* `access_keys_authentication_disabled` - Whether access key authentication is disabled. + * `redis_configuration` - A `redis_configuration` block as defined below. * `zones` - A list of Availability Zones in which this Redis Cache is located. diff --git a/website/docs/r/redis_cache.html.markdown b/website/docs/r/redis_cache.html.markdown index 5f504153f6d9..1c4e6d25a060 100644 --- a/website/docs/r/redis_cache.html.markdown +++ b/website/docs/r/redis_cache.html.markdown @@ -59,6 +59,8 @@ The following arguments are supported: --- +* `access_keys_authentication_disabled` - (Optional) Disable access key authentication. Microsoft Entra (AAD) authentication (`active_directory_authentication_enabled`) must be enabled to disable this. Defaults to `false`. + * `enable_non_ssl_port` - (Optional) Enable the non-SSL port (6379) - disabled by default. * `identity` - (Optional) An `identity` block as defined below.