diff --git a/infra/shared/terraform/modules/app-service/main.tf b/infra/shared/terraform/modules/app-service/main.tf
index abb0f96b..b6ae0647 100644
--- a/infra/shared/terraform/modules/app-service/main.tf
+++ b/infra/shared/terraform/modules/app-service/main.tf
@@ -48,7 +48,8 @@ resource "azurerm_linux_web_app" "application" {
virtual_network_subnet_id = var.appsvc_subnet_id
identity {
- type = "SystemAssigned"
+ type = var.identity.type
+ identity_ids = var.identity.type == "SystemAssigned" ? [] : var.identity.identity_ids
}
tags = {
@@ -107,9 +108,9 @@ resource "azurerm_linux_web_app" "application" {
SPRING_CLOUD_AZURE_ACTIVE_DIRECTORY_CREDENTIAL_CLIENT_SECRET = var.contoso_webapp_options.contoso_active_directory_client_secret
SPRING_CLOUD_AZURE_ACTIVE_DIRECTORY_PROFILE_TENANT_ID = var.contoso_webapp_options.contoso_active_directory_tenant_id
- SPRING_DATA_REDIS_HOST = var.contoso_webapp_options.redis_host_name
- SPRING_DATA_REDIS_PORT = var.contoso_webapp_options.redis_port
- SPRING_DATA_REDIS_PASSWORD = var.contoso_webapp_options.redis_password
+ AZURE_CACHE_REDIS_HOST = var.contoso_webapp_options.redis_host_name
+ AZURE_CACHE_REDIS_PORT = var.contoso_webapp_options.redis_port
+ AZURE_CACHE_REDIS_CLIENT_ID = var.contoso_webapp_options.redis_user_client_id
CONTOSO_RETRY_DEMO = "0"
}
diff --git a/infra/shared/terraform/modules/app-service/variables.tf b/infra/shared/terraform/modules/app-service/variables.tf
index a9789967..292c16db 100644
--- a/infra/shared/terraform/modules/app-service/variables.tf
+++ b/infra/shared/terraform/modules/app-service/variables.tf
@@ -59,6 +59,25 @@ variable "public_network_access_enabled" {
description = "Should public network access be enabled for the Web App."
}
+variable "identity" {
+ type = object({
+ type = string
+ identity_ids = optional(list(string))
+ })
+
+ description = "The identity type and the list of identities ids"
+
+ default = {
+ type = "SystemAssigned"
+ identity_ids = []
+ }
+
+ validation {
+ condition = contains(["SystemAssigned", "UserAssigned", "SystemAssigned, UserAssigned"], var.identity.type)
+ error_message = "Please, choose among one of the following identity types: SystemAssigned, UserAssigned or SystemAssigned, UserAssigned."
+ }
+}
+
variable "contoso_webapp_options" {
type = object({
contoso_active_directory_tenant_id = string
@@ -71,7 +90,7 @@ variable "contoso_webapp_options" {
redis_host_name = string
redis_port = number
- redis_password = string
+ redis_user_client_id = string
})
description = "The options for the webapp"
diff --git a/infra/shared/terraform/modules/cache/main.tf b/infra/shared/terraform/modules/cache/main.tf
index 984c4060..353127c5 100644
--- a/infra/shared/terraform/modules/cache/main.tf
+++ b/infra/shared/terraform/modules/cache/main.tf
@@ -24,8 +24,9 @@ resource "azurerm_redis_cache" "cache" {
# public network access will be allowed for non-prod so devs can do integration testing while debugging locally
public_network_access_enabled = var.environment == "prod" ? false : true
- # https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-configure#default-redis-server-configuration
redis_configuration {
+ enable_authentication = true
+ active_directory_authentication_enabled = true
}
}
diff --git a/infra/shared/terraform/modules/cache/outputs.tf b/infra/shared/terraform/modules/cache/outputs.tf
index acfb994d..96231e29 100644
--- a/infra/shared/terraform/modules/cache/outputs.tf
+++ b/infra/shared/terraform/modules/cache/outputs.tf
@@ -1,6 +1,6 @@
-output "cache_secret" {
- value = azurerm_redis_cache.cache.primary_access_key
- description = "The secret to use when connecting to Azure Cache for Redis"
+output "cache_id" {
+ value = azurerm_redis_cache.cache.id
+ description = "The id of the Azure Cache for Redis"
}
output "cache_hostname" {
diff --git a/infra/terraform/application.tf b/infra/terraform/application.tf
index 55c9d9d0..5f97c18f 100644
--- a/infra/terraform/application.tf
+++ b/infra/terraform/application.tf
@@ -22,16 +22,23 @@ module "application" {
frontdoor_profile_uuid = module.frontdoor[0].resource_guid
public_network_access_enabled = false
+ identity = {
+ type = "SystemAssigned, UserAssigned"
+ identity_ids = [
+ azurerm_user_assigned_identity.primary_app_service_identity[0].id
+ ]
+ }
+
contoso_webapp_options = {
- contoso_active_directory_tenant_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_tenant_id[0].id})"
- contoso_active_directory_client_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_client_id[0].id})"
- contoso_active_directory_client_secret = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_client_secret[0].id})"
- postgresql_database_url = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_database_url[0].id})"
- postgresql_database_user = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_database_admin[0].id})"
- postgresql_database_password = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_database_admin_password[0].id})"
- redis_host_name = module.cache[0].cache_hostname
- redis_port = module.cache[0].cache_ssl_port
- redis_password = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_cache_secret[0].id})"
+ contoso_active_directory_tenant_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_tenant_id[0].id})"
+ contoso_active_directory_client_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_client_id[0].id})"
+ contoso_active_directory_client_secret = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_client_secret[0].id})"
+ postgresql_database_url = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_database_url[0].id})"
+ postgresql_database_user = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_database_admin[0].id})"
+ postgresql_database_password = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_database_admin_password[0].id})"
+ redis_host_name = module.cache[0].cache_hostname
+ redis_port = module.cache[0].cache_ssl_port
+ redis_user_client_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.primary_redis_user_secret[0].id})"
}
}
@@ -48,23 +55,30 @@ module "secondary_application" {
location = var.secondary_location
private_dns_resource_group = azurerm_resource_group.hub[0].name
appsvc_subnet_id = module.secondary_spoke_vnet[0].subnets[local.app_service_subnet_name].id
- private_endpoint_subnet_id = module.secondary_spoke_vnet[0].subnets[local.private_link_subnet_name].id
+ private_endpoint_subnet_id = module.secondary_spoke_vnet[0].subnets[local.private_link_subnet_name].id
app_insights_connection_string = module.hub_app_insights[0].connection_string
log_analytics_workspace_id = module.hub_app_insights[0].log_analytics_workspace_id
frontdoor_host_name = module.frontdoor[0].host_name
frontdoor_profile_uuid = module.frontdoor[0].resource_guid
public_network_access_enabled = false
+ identity = {
+ type = "SystemAssigned, UserAssigned"
+ identity_ids = [
+ azurerm_user_assigned_identity.secondary_app_service_identity[0].id
+ ]
+ }
+
contoso_webapp_options = {
- contoso_active_directory_tenant_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_tenant_id[0].id})"
- contoso_active_directory_client_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_client_id[0].id})"
- contoso_active_directory_client_secret = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_client_secret[0].id})"
- postgresql_database_url = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.secondary_contoso_database_url[0].id})"
- postgresql_database_user = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_database_admin[0].id})"
- postgresql_database_password = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_database_admin_password[0].id})"
- redis_host_name = module.secondary_cache[0].cache_hostname
- redis_port = module.secondary_cache[0].cache_ssl_port
- redis_password = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_cache_secret[0].id})"
+ contoso_active_directory_tenant_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_tenant_id[0].id})"
+ contoso_active_directory_client_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_client_id[0].id})"
+ contoso_active_directory_client_secret = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_application_client_secret[0].id})"
+ postgresql_database_url = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.secondary_contoso_database_url[0].id})"
+ postgresql_database_user = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_database_admin[0].id})"
+ postgresql_database_password = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.contoso_database_admin_password[0].id})"
+ redis_host_name = module.secondary_cache[0].cache_hostname
+ redis_port = module.secondary_cache[0].cache_ssl_port
+ redis_user_client_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.secondary_redis_user_secret[0].id})"
}
}
@@ -92,6 +106,13 @@ module "dev_application" {
frontdoor_profile_uuid = module.dev_frontdoor[0].resource_guid
public_network_access_enabled = true
+ identity = {
+ type = "SystemAssigned, UserAssigned"
+ identity_ids = [
+ azurerm_user_assigned_identity.dev_app_service_identity[0].id
+ ]
+ }
+
contoso_webapp_options = {
contoso_active_directory_tenant_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.dev_contoso_application_tenant_id[0].id})"
contoso_active_directory_client_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.dev_contoso_application_client_id[0].id})"
@@ -101,6 +122,6 @@ module "dev_application" {
postgresql_database_password = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.dev_contoso_database_admin_password[0].id})"
redis_host_name = module.dev-cache[0].cache_hostname
redis_port = module.dev-cache[0].cache_ssl_port
- redis_password = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.dev_contoso_cache_secret[0].id})"
+ redis_user_client_id = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.dev_redis_user_secret[0].id})"
}
}
diff --git a/infra/terraform/cache.tf b/infra/terraform/cache.tf
index b92723fb..72704a17 100644
--- a/infra/terraform/cache.tf
+++ b/infra/terraform/cache.tf
@@ -14,6 +14,30 @@ module "cache" {
log_analytics_workspace_id = module.hub_app_insights[0].log_analytics_workspace_id
}
+
+resource "azurerm_redis_cache_access_policy_assignment" "primary_current_user" {
+ count = var.environment == "prod" ? 1 : 0
+ name = "primarycurrentuser"
+ redis_cache_id = module.cache[0].cache_id
+ access_policy_name = "Data Contributor"
+ object_id = data.azuread_client_config.current.object_id
+ object_id_alias = "currentuser"
+}
+
+resource "azurerm_redis_cache_access_policy_assignment" "app_user" {
+ count = var.environment == "prod" ? 1 : 0
+ name = "primaryappuser"
+ redis_cache_id = module.cache[0].cache_id
+ access_policy_name = "Data Contributor"
+ object_id = azurerm_user_assigned_identity.primary_app_service_identity[0].principal_id
+ object_id_alias = azurerm_user_assigned_identity.primary_app_service_identity[0].principal_id
+
+ # Ensure that the current user has been created before creating the app user
+ depends_on = [
+ azurerm_redis_cache_access_policy_assignment.primary_current_user
+ ]
+}
+
# ----------------------------------------------------------------------------------------------
# Cache - Prod - Secondary Region
# ----------------------------------------------------------------------------------------------
@@ -29,6 +53,29 @@ module "secondary_cache" {
log_analytics_workspace_id = module.hub_app_insights[0].log_analytics_workspace_id
}
+resource "azurerm_redis_cache_access_policy_assignment" "secondary_current_user" {
+ count = var.environment == "prod" ? 1 : 0
+ name = "secondarycurrentuser"
+ redis_cache_id = module.secondary_cache[0].cache_id
+ access_policy_name = "Data Contributor"
+ object_id = data.azuread_client_config.current.object_id
+ object_id_alias = "currentuser"
+}
+
+resource "azurerm_redis_cache_access_policy_assignment" "secondary_app_user" {
+ count = var.environment == "prod" ? 1 : 0
+ name = "secondaryappuser"
+ redis_cache_id = module.secondary_cache[0].cache_id
+ access_policy_name = "Data Contributor"
+ object_id = azurerm_user_assigned_identity.secondary_app_service_identity[0].principal_id
+ object_id_alias = azurerm_user_assigned_identity.secondary_app_service_identity[0].principal_id
+
+ # Ensure that the current user has been created before creating the app user
+ depends_on = [
+ azurerm_redis_cache_access_policy_assignment.secondary_current_user
+ ]
+}
+
# ----------------------------------------------------------------------------------------------
# Cache - Dev
# ----------------------------------------------------------------------------------------------
@@ -43,3 +90,26 @@ module "dev-cache" {
private_endpoint_subnet_id = null
log_analytics_workspace_id = module.dev_app_insights[0].log_analytics_workspace_id
}
+
+resource "azurerm_redis_cache_access_policy_assignment" "dev_current_user" {
+ count = var.environment == "dev" ? 1 : 0
+ name = "devcurrentuser"
+ redis_cache_id = module.dev-cache[0].cache_id
+ access_policy_name = "Data Contributor"
+ object_id = data.azuread_client_config.current.object_id
+ object_id_alias = "currentuser"
+}
+
+resource "azurerm_redis_cache_access_policy_assignment" "dev_app_user" {
+ count = var.environment == "dev" ? 1 : 0
+ name = "devappuser"
+ redis_cache_id = module.dev-cache[0].cache_id
+ access_policy_name = "Data Contributor"
+ object_id = azurerm_user_assigned_identity.dev_app_service_identity[0].principal_id
+ object_id_alias = azurerm_user_assigned_identity.dev_app_service_identity[0].principal_id
+
+ # Ensure that the current user has been created before creating the app user
+ depends_on = [
+ azurerm_redis_cache_access_policy_assignment.dev_current_user
+ ]
+}
diff --git a/infra/terraform/identity.tf b/infra/terraform/identity.tf
new file mode 100644
index 00000000..74d2b7ff
--- /dev/null
+++ b/infra/terraform/identity.tf
@@ -0,0 +1,54 @@
+# ------------------------------------------------
+# Identity for the Production Primary App Service
+# ------------------------------------------------
+
+resource "azurecaf_name" "primary_app_service_identity_name" {
+ count = var.environment == "prod" ? 1 : 0
+ name = var.application_name
+ resource_type = "azurerm_user_assigned_identity"
+ suffixes = [var.location, var.environment]
+}
+
+resource "azurerm_user_assigned_identity" "primary_app_service_identity" {
+ count = var.environment == "prod" ? 1 : 0
+ location = azurerm_resource_group.spoke[0].location
+ name = azurecaf_name.primary_app_service_identity_name[0].result
+ resource_group_name = azurerm_resource_group.spoke[0].name
+}
+
+# ------------------------------------------------
+# Identity for the Production Secondary App Service
+# ------------------------------------------------
+
+resource "azurecaf_name" "secondary_app_service_identity_name" {
+ count = var.environment == "prod" ? 1 : 0
+ name = var.application_name
+ resource_type = "azurerm_user_assigned_identity"
+ suffixes = [var.secondary_location, var.environment]
+}
+
+resource "azurerm_user_assigned_identity" "secondary_app_service_identity" {
+ count = var.environment == "prod" ? 1 : 0
+ location = azurerm_resource_group.secondary_spoke[0].location
+ name = azurecaf_name.secondary_app_service_identity_name[0].result
+ resource_group_name = azurerm_resource_group.secondary_spoke[0].name
+}
+
+
+# ------------------------------------------------
+# Identity for the Production Dev App Service
+# ------------------------------------------------
+
+resource "azurecaf_name" "dev_app_service_identity_name" {
+ count = var.environment == "dev" ? 1 : 0
+ name = var.application_name
+ resource_type = "azurerm_user_assigned_identity"
+ suffixes = [var.location, var.environment]
+}
+
+resource "azurerm_user_assigned_identity" "dev_app_service_identity" {
+ count = var.environment == "dev" ? 1 : 0
+ location = azurerm_resource_group.dev[0].location
+ name = azurecaf_name.dev_app_service_identity_name[0].result
+ resource_group_name = azurerm_resource_group.dev[0].name
+}
diff --git a/infra/terraform/secrets.tf b/infra/terraform/secrets.tf
index 94f3dded..d7c66e5b 100644
--- a/infra/terraform/secrets.tf
+++ b/infra/terraform/secrets.tf
@@ -94,10 +94,10 @@ resource "azurerm_key_vault_secret" "contoso_application_client_secret" {
]
}
-resource "azurerm_key_vault_secret" "contoso_cache_secret" {
+resource "azurerm_key_vault_secret" "primary_redis_user_secret" {
count = var.environment == "prod" ? 1 : 0
- name = "contoso-redis-password"
- value = module.cache[0].cache_secret
+ name = "contoso-primary-redis-user-object-id"
+ value = azurerm_user_assigned_identity.primary_app_service_identity[0].client_id
key_vault_id = module.hub_key_vault[0].vault_id
depends_on = [
azurerm_role_assignment.kv_administrator_user_role_assignement
@@ -128,6 +128,16 @@ resource "azurerm_key_vault_secret" "secondary_contoso_database_url" {
]
}
+resource "azurerm_key_vault_secret" "secondary_redis_user_secret" {
+ count = var.environment == "prod" ? 1 : 0
+ name = "contoso-secondary-redis-user-object-id"
+ value = azurerm_user_assigned_identity.secondary_app_service_identity[0].client_id
+ key_vault_id = module.hub_key_vault[0].vault_id
+ depends_on = [
+ azurerm_role_assignment.kv_administrator_user_role_assignement
+ ]
+}
+
# Give the app access to the key vault secrets - https://learn.microsoft.com/azure/key-vault/general/rbac-guide?tabs=azure-cli#secret-scope-role-assignment
resource azurerm_role_assignment app_keyvault_role_assignment {
count = var.environment == "prod" ? 1 : 0
@@ -216,10 +226,10 @@ resource "azurerm_key_vault_secret" "dev_contoso_application_client_secret" {
]
}
-resource "azurerm_key_vault_secret" "dev_contoso_cache_secret" {
+resource "azurerm_key_vault_secret" "dev_redis_user_secret" {
count = var.environment == "dev" ? 1 : 0
- name = "contoso-redis-password"
- value = module.dev-cache[0].cache_secret
+ name = "contoso-dev-redis-user-object-id"
+ value = azurerm_user_assigned_identity.dev_app_service_identity[0].client_id
key_vault_id = module.dev_key_vault[0].vault_id
depends_on = [
azurerm_role_assignment.dev_kv_administrator_user_role_assignement
diff --git a/infra/terraform/versions.tf b/infra/terraform/versions.tf
index db30b3bf..53ce0610 100644
--- a/infra/terraform/versions.tf
+++ b/infra/terraform/versions.tf
@@ -2,7 +2,7 @@ terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
- version = "3.112.0"
+ version = "3.114.0"
}
azurecaf = {
source = "aztfmod/azurecaf"
diff --git a/src/contoso-fiber/pom.xml b/src/contoso-fiber/pom.xml
index fb2f5a80..2f47a70a 100644
--- a/src/contoso-fiber/pom.xml
+++ b/src/contoso-fiber/pom.xml
@@ -6,7 +6,7 @@
org.springframework.boot
spring-boot-starter-parent
- 3.1.2
+ 3.2.5
@@ -18,8 +18,8 @@
17
- 5.5.0
- 2022.0.4
+ 5.14.0
+ 2023.0.2
@@ -71,6 +71,10 @@
org.springframework.boot
spring-boot-starter-data-redis
+
+ com.azure.spring
+ spring-cloud-azure-starter-data-redis-lettuce
+
org.springframework.session
spring-session-data-redis
@@ -100,12 +104,10 @@
nz.net.ultraq.thymeleaf
thymeleaf-layout-dialect
- 3.2.1
com.fasterxml.jackson.core
jackson-databind
- 2.15.2
@@ -123,6 +125,11 @@
spring-boot-starter-aop
+
+ org.apache.commons
+ commons-lang3
+
+
diff --git a/src/contoso-fiber/src/main/resources/application.properties b/src/contoso-fiber/src/main/resources/application.properties
index 05ceab10..3e439bfb 100644
--- a/src/contoso-fiber/src/main/resources/application.properties
+++ b/src/contoso-fiber/src/main/resources/application.properties
@@ -5,6 +5,10 @@ spring.jpa.hibernate.ddl-auto=validate
spring.cloud.azure.active-directory.enabled=true
# Redis
+spring.data.redis.host=${AZURE_CACHE_REDIS_HOST}
+spring.data.redis.port=${AZURE_CACHE_REDIS_PORT}
+spring.data.redis.azure.passwordless-enabled=true
+spring.data.redis.azure.credential.client-id=${AZURE_CACHE_REDIS_CLIENT_ID}
spring.data.redis.ssl.enabled=true
# Spring Session to leverage Redis to back a web application’s HttpSession