diff --git a/internal/services/redis/redis_cache_resource.go b/internal/services/redis/redis_cache_resource.go index 146d008911f6..594f325888b5 100644 --- a/internal/services/redis/redis_cache_resource.go +++ b/internal/services/redis/redis_cache_resource.go @@ -145,6 +145,10 @@ func resourceRedisCache() *pluginsdk.Resource { MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ + "active_directory_authentication_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + }, "maxclients": { Type: pluginsdk.TypeInt, Computed: true, @@ -466,7 +470,7 @@ func resourceRedisCacheCreate(d *pluginsdk.ResourceData, meta interface{}) error return fmt.Errorf("internal-error: context had no deadline") } stateConf := &pluginsdk.StateChangeConf{ - Pending: []string{"Scaling", "Updating", "Creating"}, + Pending: []string{"Scaling", "Updating", "Creating", "ConfiguringAAD"}, Target: []string{"Succeeded"}, Refresh: redisStateRefreshFunc(ctx, client, id), MinTimeout: 15 * time.Second, @@ -570,7 +574,7 @@ func resourceRedisCacheUpdate(d *pluginsdk.ResourceData, meta interface{}) error log.Printf("[DEBUG] Waiting for %s to become available", *id) stateConf := &pluginsdk.StateChangeConf{ - Pending: []string{"Scaling", "Updating", "Creating", "UpgradingRedisServerVersion"}, + Pending: []string{"Scaling", "Updating", "Creating", "UpgradingRedisServerVersion", "ConfiguringAAD"}, Target: []string{"Succeeded"}, Refresh: redisStateRefreshFunc(ctx, client, *id), MinTimeout: 15 * time.Second, @@ -821,9 +825,24 @@ func expandRedisConfiguration(d *pluginsdk.ResourceData) (*redis.RedisCommonProp output.MaxmemoryPolicy = utils.String(v) } + // AAD/Entra support + // nolint : staticcheck + v, valExists := d.GetOkExists("redis_configuration.0.active_directory_authentication_enabled") + if valExists { + entraEnabled := v.(bool) + + // active_directory_authentication_enabled is available when SKU is Premium + if strings.EqualFold(skuName, string(redis.SkuNamePremium)) { + + output.AadEnabled = utils.String(strconv.FormatBool(entraEnabled)) + } else if entraEnabled && !strings.EqualFold(skuName, string(redis.SkuNamePremium)) { + return nil, fmt.Errorf("The `active_directory_authentication_enabled` property requires a `Premium` sku to be set") + } + } + // RDB Backup // nolint : staticcheck - v, valExists := d.GetOkExists("redis_configuration.0.rdb_backup_enabled") + v, valExists = d.GetOkExists("redis_configuration.0.rdb_backup_enabled") if valExists { rdbBackupEnabled := v.(bool) @@ -939,6 +958,14 @@ func flattenTenantSettings(input *map[string]string) map[string]string { func flattenRedisConfiguration(input *redis.RedisCommonPropertiesRedisConfiguration) ([]interface{}, error) { outputs := make(map[string]interface{}) + if input.AadEnabled != nil { + a, err := strconv.ParseBool(*input.AadEnabled) + if err != nil { + return nil, fmt.Errorf("parsing `aad-enabled` %q: %+v", *input.AadEnabled, err) + } + outputs["active_directory_authentication_enabled"] = a + } + if input.Maxclients != nil { i, err := strconv.Atoi(*input.Maxclients) if err != nil { diff --git a/internal/services/redis/redis_cache_resource_test.go b/internal/services/redis/redis_cache_resource_test.go index d511c817835f..f551f322a52e 100644 --- a/internal/services/redis/redis_cache_resource_test.go +++ b/internal/services/redis/redis_cache_resource_test.go @@ -131,6 +131,21 @@ func TestAccRedisCache_premiumShardedScaling(t *testing.T) { }) } +func TestAccRedisCache_AadEnabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_redis_cache", "test") + r := RedisCacheResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.aadEnabled(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("redis_configuration.0.rdb_storage_connection_string"), + }) +} + func TestAccRedisCache_BackupDisabled(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_redis_cache", "test") r := RedisCacheResource{} @@ -709,6 +724,34 @@ resource "azurerm_redis_cache" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } +func (RedisCacheResource) aadEnabled(data acceptance.TestData) 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 = 3 + family = "P" + sku_name = "Premium" + enable_non_ssl_port = false + public_network_access_enabled = false + + redis_configuration { + active_directory_authentication_enabled = true + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + func (RedisCacheResource) backupDisabled(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/r/redis_cache.html.markdown b/website/docs/r/redis_cache.html.markdown index d08cf04e420d..7dd1cc421b8e 100644 --- a/website/docs/r/redis_cache.html.markdown +++ b/website/docs/r/redis_cache.html.markdown @@ -140,6 +140,8 @@ redis_configuration { -> **NOTE:** `enable_authentication` can only be set to `false` if a `subnet_id` is specified; and only works if there aren't existing instances within the subnet with `enable_authentication` set to `true`. +* `active_directory_authentication_enabled` - (Optional) Enable Microsoft Entra (AAD) authentication. Defaults to `false`. + * `maxmemory_reserved` - (Optional) Value in megabytes reserved for non-cache usage e.g. failover. Defaults are shown below. * `maxmemory_delta` - (Optional) The max-memory delta for this Redis instance. Defaults are shown below. * `maxmemory_policy` - (Optional) How Redis will select what to remove when `maxmemory` is reached. Defaults to `volatile-lru`.