diff --git a/.env.example b/.env.example index fdc867d..10bfca2 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,6 @@ # All Values Required AZURE_ENV= ENGINE_APP_ID= -ENGINE_APP_SECRET= UI_APP_ID= TENANT_ID= COSMOS_URL=https://.documents.azure.com @@ -9,3 +8,4 @@ KEYVAULT_URL=https://.vault.azure.net # Legacy Values # COSMOS_KEY= +# ENGINE_APP_SECRET= \ No newline at end of file diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index dec530d..5e3d1ed 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -647,11 +647,6 @@ process { -Scope $scope ` | Out-Null - Write-Host "INFO: Creating Azure IPAM Engine Secret" -ForegroundColor Green - - # Create IPAM Engine Secret - $engineSecret = New-AzADAppCredential -ApplicationObject $engineObject -StartDate (Get-Date) -EndDate (Get-Date).AddYears(2) - if (-not $DisableUI) { Write-Host "INFO: Azure IPAM Engine & UI Applications/Service Principals created successfully" -ForegroundColor Green } @@ -661,7 +656,6 @@ process { $appDetails = @{ EngineAppId = $engineApp.AppId - EngineSecret = $engineSecret.SecretText } # Add UI AppID to AppDetails (If DisableUI not specified) @@ -796,8 +790,6 @@ process { [string]$UIAppId = [GUID]::Empty, [Parameter(Mandatory = $true)] [string]$EngineAppId, - [Parameter(Mandatory = $true)] - [string]$EngineSecret, [Parameter(Mandatory = $false)] [bool]$DisableUI = $false ) @@ -809,14 +801,13 @@ process { # Update Parameter Values $parametersObject.parameters.engineAppId.value = $EngineAppId - $parametersObject.parameters.engineAppSecret.value = $EngineSecret if (-not $DisableUI) { $parametersObject.parameters.uiAppId.value = $UIAppId - $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property uiAppId, engineAppId, engineAppSecret + $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property uiAppId, engineAppId } else { - $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property engineAppId, engineAppSecret + $parametersObject.parameters = $parametersObject.parameters | Select-Object -Property engineAppId } # Output updated parameter file for Bicep deployment @@ -839,13 +830,12 @@ process { # Read Values from Parameters $UIAppId = $parametersObject.parameters.uiAppId.value ?? [GUID]::Empty $EngineAppId = $parametersObject.parameters.engineAppId.value - $EngineSecret = $parametersObject.parameters.engineAppSecret.value $script:DisableUI = ($UIAppId -eq [GUID]::Empty) ? $true : $false - if ((-not $EngineAppId) -or (-not $EngineSecret)) { + if ((-not $EngineAppId)) { Write-Host "ERROR: Missing required parameters from Bicep parameter file" -ForegroundColor Red Write-Host "ERROR: Please ensure the following parameters are present in the Bicep parameter file" -ForegroundColor Red - Write-Host "ERROR: Required: [engineAppId, engineAppSecret]" -ForegroundColor Red + Write-Host "ERROR: Required: [engineAppId]" -ForegroundColor Red Write-Host "" Write-Host "ERROR: Please refer to the deployment documentation for more information" -ForegroundColor Red Write-Host "ERROR: " -ForegroundColor Red -NoNewline @@ -862,7 +852,6 @@ process { $appDetails = @{ UIAppId = $UIAppId EngineAppId = $EngineAppId - EngineSecret = $EngineSecret } return $appDetails @@ -875,8 +864,6 @@ process { [Parameter(Mandatory = $true)] [string]$EngineAppId, [Parameter(Mandatory = $true)] - [string]$EngineSecret, - [Parameter(Mandatory = $false)] [string]$NamePrefix, [Parameter(Mandatory = $false)] [string]$AzureCloud, @@ -897,7 +884,6 @@ process { # Instantiate deployment parameter object $deploymentParameters = @{ engineAppId = $EngineAppId - engineAppSecret = $EngineSecret uiAppId = $UiAppId } @@ -1056,6 +1042,26 @@ process { } while ($publishSuccess -eq $False -and $publishRetries -ge 0) } + Function New-FederatedCredential { + Param( + [Parameter(Mandatory = $true)] + [string]$EngineAppId, + [Parameter(Mandatory = $true)] + [string]$ManagedIdentityPrincipalId + ) + + Write-Host "INFO: Creating Federated Credential" -ForegroundColor Green + + New-AzADAppFederatedCredential ` + -ApplicationObjectId (Get-AzADApplication -ApplicationId $EngineAppId).Id ` + -Audience "api://AzureADTokenExchange" ` + -Issuer "https://login.microsoftonline.com/$($(Get-AzContext).Tenant.Id)/v2.0" ` + -Subject $ManagedIdentityPrincipalId ` + -Name "ipam-engine-mi-credential" | Out-Null + + Write-Host "INFO: Federated Credential Created" -ForegroundColor Green + } + Function Update-UIApplication { Param( [Parameter(Mandatory = $true)] @@ -1200,6 +1206,12 @@ process { -Endpoint $deployment.Outputs["appServiceHostName"].Value } + if ($PSCmdlet.ParameterSetName -notin 'AppsOnly') { + New-FederatedCredential ` + -EngineAppId $appDetails.EngineAppId ` + -ManagedIdentityPrincipalId $deployment.Outputs["managedIdentityPrincipalId"].Value + } + if ($PSCmdlet.ParameterSetName -in ('App', 'Function')) { if (-Not $ZipFilePath) { try { diff --git a/deploy/main.bicep b/deploy/main.bicep index db0ee6e..32ced89 100644 --- a/deploy/main.bicep +++ b/deploy/main.bicep @@ -29,10 +29,6 @@ param uiAppId string = '00000000-0000-0000-0000-000000000000' @description('IPAM-Engine App Registration Client/App ID') param engineAppId string -@secure() -@description('IPAM-Engine App Registration Client Secret') -param engineAppSecret string - @description('Tags') param tags object = {} @@ -92,7 +88,6 @@ module keyVault './modules/keyVault.bicep' = { identityClientId: managedIdentity.outputs.clientId uiAppId: uiAppId engineAppId: engineAppId - engineAppSecret: engineAppSecret workspaceId: logAnalyticsWorkspace.outputs.workspaceId } } @@ -186,3 +181,4 @@ output appServiceName string = deployAsFunc ? resourceNames.functionName : resou output appServiceHostName string = deployAsFunc ? functionApp!.outputs.functionAppHostName : appService!.outputs.appServiceHostName output acrName string = privateAcr ? containerRegistry!.outputs.acrName : '' output acrUri string = privateAcr ? containerRegistry!.outputs.acrUri : '' +output managedIdentityPrincipalId string = managedIdentity.outputs.principalId diff --git a/deploy/main.parameters.example.json b/deploy/main.parameters.example.json index e9c5632..af2b037 100644 --- a/deploy/main.parameters.example.json +++ b/deploy/main.parameters.example.json @@ -17,9 +17,6 @@ "engineAppId": { "value": "" }, - "engineAppSecret": { - "value": "" - }, "deployAsFunc": { "value": false }, diff --git a/deploy/modules/appService.bicep b/deploy/modules/appService.bicep index ed24516..b40256f 100644 --- a/deploy/modules/appService.bicep +++ b/deploy/modules/appService.bicep @@ -116,10 +116,6 @@ resource appService 'Microsoft.Web/sites@2021-02-01' = { name: 'ENGINE_APP_ID' value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/ENGINE-ID/)' } - { - name: 'ENGINE_APP_SECRET' - value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/ENGINE-SECRET/)' - } { name: 'TENANT_ID' value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/TENANT-ID/)' diff --git a/deploy/modules/functionApp.bicep b/deploy/modules/functionApp.bicep index 05650e9..107f946 100644 --- a/deploy/modules/functionApp.bicep +++ b/deploy/modules/functionApp.bicep @@ -119,10 +119,6 @@ resource functionApp 'Microsoft.Web/sites@2021-03-01' = { name: 'ENGINE_APP_ID' value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/ENGINE-ID/)' } - { - name: 'ENGINE_APP_SECRET' - value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/ENGINE-SECRET/)' - } { name: 'TENANT_ID' value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/TENANT-ID/)' @@ -248,7 +244,7 @@ resource diagnosticSettingsApp 'Microsoft.Insights/diagnosticSettings@2021-05-01 enabled: true retentionPolicy: { days: 0 - enabled: false + enabled: false } } ] diff --git a/deploy/modules/keyVault.bicep b/deploy/modules/keyVault.bicep index e2a4fdd..accc340 100644 --- a/deploy/modules/keyVault.bicep +++ b/deploy/modules/keyVault.bicep @@ -19,10 +19,6 @@ param uiAppId string @description('IPAM-Engine App Registration Client/App ID') param engineAppId string -@secure() -@description('IPAM-Engine App Registration Client Secret') -param engineAppSecret string - @description('Log Analytics Worskpace ID') param workspaceId string @@ -73,14 +69,6 @@ resource engineId 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { } } -resource engineSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { - parent: keyVault - name: 'ENGINE-SECRET' - properties: { - value: engineAppSecret - } -} - resource appTenant 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { parent: keyVault name: 'TENANT-ID' diff --git a/engine/app/routers/common/helper.py b/engine/app/routers/common/helper.py index 80ca1cc..74640db 100644 --- a/engine/app/routers/common/helper.py +++ b/engine/app/routers/common/helper.py @@ -11,6 +11,7 @@ from azure.cosmos.aio import CosmosClient from azure.identity.aio import ( ClientSecretCredential, + ClientAssertionCredential, ManagedIdentityCredential, OnBehalfOfCredential, ) @@ -107,44 +108,51 @@ def get_user_id_from_jwt(token): return decoded['oid'] -async def get_obo_token(assertion): - """DOCSTRING""" - - azure_arm_url = 'https://{}/user_impersonation'.format(globals.AZURE_ARM_URL) - - credential = OnBehalfOfCredential( - tenant_id=globals.TENANT_ID, - client_id=globals.CLIENT_ID, - client_secret=globals.CLIENT_SECRET, - user_assertion=assertion - ) - obo_token = await credential.get_token(azure_arm_url) - await credential.close() - - return obo_token - async def get_client_credentials(): """DOCSTRING""" - credential = ClientSecretCredential( - tenant_id=globals.TENANT_ID, - client_id=globals.CLIENT_ID, - client_secret=globals.CLIENT_SECRET, - authority=globals.AUTHORITY_HOST - ) + # If CLIENT_SECRET is defined, use it for credentials. Otherwise, use the Managed Identity + if globals.CLIENT_SECRET: + credential = ClientSecretCredential( + tenant_id=globals.TENANT_ID, + client_id=globals.CLIENT_ID, + client_secret=globals.CLIENT_SECRET, + authority=globals.AUTHORITY_HOST + ) + else: + mi_token = await managed_identity_credential.get_token("api://AzureADTokenExchange") + + credential = ClientAssertionCredential( + tenant_id=globals.TENANT_ID, + client_id=globals.CLIENT_ID, + func=lambda: mi_token.token, + authority=globals.AUTHORITY_HOST + ) return credential async def get_obo_credentials(assertion): """DOCSTRING""" - credential = OnBehalfOfCredential( - tenant_id=globals.TENANT_ID, - client_id=globals.CLIENT_ID, - client_secret=globals.CLIENT_SECRET, - user_assertion=assertion, - authority=globals.AUTHORITY_HOST - ) + # If CLIENT_SECRET is defined, use it for credentials. Otherwise, use the Managed Identity + if globals.CLIENT_SECRET: + credential = OnBehalfOfCredential( + tenant_id=globals.TENANT_ID, + client_id=globals.CLIENT_ID, + client_secret=globals.CLIENT_SECRET, + user_assertion=assertion, + authority=globals.AUTHORITY_HOST + ) + else: + mi_token = await managed_identity_credential.get_token("api://AzureADTokenExchange") + + credential = OnBehalfOfCredential( + tenant_id=globals.TENANT_ID, + client_id=globals.CLIENT_ID, + client_assertion_func=lambda: mi_token.token, + user_assertion=assertion, + authority=globals.AUTHORITY_HOST + ) return credential diff --git a/engine/requirements.lock.txt b/engine/requirements.lock.txt index 1fdd64c..c616d27 100644 --- a/engine/requirements.lock.txt +++ b/engine/requirements.lock.txt @@ -19,7 +19,7 @@ azure-mgmt-datafactory==9.2.0 azure-mgmt-managementgroups==1.0.0 azure-mgmt-network==29.0.0 azure-mgmt-resource==25.0.0 -azure-mgmt-resource-subscriptions==1.1.0 +azure-mgmt-resource-subscriptions==1.0.0b1 azure-mgmt-resourcegraph==8.0.0 certifi==2025.8.3 cffi==2.0.0 diff --git a/migrate/modules/appService.bicep b/migrate/modules/appService.bicep index c4da561..760ce1d 100644 --- a/migrate/modules/appService.bicep +++ b/migrate/modules/appService.bicep @@ -110,10 +110,6 @@ resource appService 'Microsoft.Web/sites@2021-02-01' = { name: 'ENGINE_APP_ID' value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/ENGINE-ID/)' } - { - name: 'ENGINE_APP_SECRET' - value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/ENGINE-SECRET/)' - } { name: 'TENANT_ID' value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/TENANT-ID/)'