From f54306cc7a93063708d54bb5ab465fb63d5d23cd Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Fri, 27 May 2022 20:20:28 -0700 Subject: [PATCH 1/9] Streamlined deployment script and updated admin detection in engine --- .vscode/settings.json | 3 +- deploy/appService.bicep | 3 +- deploy/deploy.ps1 | 597 ++++++++++++++++++--------------- deploy/main.bicep | 4 +- engine/app/routers/user.py | 5 +- ui/src/features/login/Login.js | 9 + 6 files changed, 347 insertions(+), 274 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ff30c44..5592767 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "editor.tabSize": 2 + "editor.tabSize": 2, + "editor.detectIndentation": false } \ No newline at end of file diff --git a/deploy/appService.bicep b/deploy/appService.bicep index 1e72aae..93880b4 100644 --- a/deploy/appService.bicep +++ b/deploy/appService.bicep @@ -56,6 +56,7 @@ resource appService 'Microsoft.Web/sites@2021-02-01' = { serverFarmId: appServicePlan.id keyVaultReferenceIdentity: managedIdentityId siteConfig: { + alwaysOn: true linuxFxVersion: 'COMPOSE|${dockerCompose}' acrUseManagedIdentityCreds: true acrUserManagedIdentityID: managedIdentityClientId @@ -77,7 +78,7 @@ resource appService 'Microsoft.Web/sites@2021-02-01' = { value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/ENGINE-ID/)' } { - name: 'ENGINE_SECRET' + name: 'ENGINE_APP_SECRET' value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/ENGINE-SECRET/)' } { diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index 0a3972b..a502e25 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -11,327 +11,386 @@ # Intake and set global parameters Param( - [Parameter(Mandatory=$false)] - [string]$location="westus3", - [Parameter(Mandatory=$false)] - [string]$namePrefix="ipam", - [Parameter(Mandatory=$true)] - [string]$tags + [Parameter(Mandatory = $false)] + [string]$Location = "westus3", + [Parameter(Mandatory = $false)] + [string]$UIAppName = "ipam-ui-app", + [Parameter(Mandatory = $false)] + [string]$EngineAppName = "ipam-engine-app", + [Parameter(Mandatory = $false)] + [string]$NamePrefix = "ipam", + [Parameter(Mandatory = $false)] + [hashtable]$Tags ) -$engineApiGuid = New-Guid + +$Env:SuppressAzurePowerShellBreakingChangeWarnings = $true + $logFile = "./deploy_$(get-date -format `"yyyyMMddhhmmsstt`").log" $tenantId = (Get-AzContext).Tenant.Id # Set preference variables $ErrorActionPreference = "Stop" -Function validateLocation { - # Validate Azure Region - Write-Host "INFO: Validating Azure Region selected for deployment" -ForegroundColor green - Write-Verbose -Message "Validating Azure Region selected for deployment" - - $validLocations = Get-AzLocation - if ($location -in ($validLocations | Select-Object -ExpandProperty Location)) { - foreach ($l in $validLocations) { - if ($location -eq $l.Location) { - $script:locationName = $l.DisplayName +Function Test-Location { + Param( + [Parameter(Mandatory=$true)] + [string]$Location + ) - Write-Host "INFO: Azure Region validated successfully" -ForegroundColor green - Write-Verbose -Message "Azure Region validated successfully" - } - } - } - else { - Write-Host "ERROR: Location provided is not a valid Azure Region!" -ForegroundColor red - exit - - } + $validLocations = Get-AzLocation | Select-Object -ExpandProperty Location + return $validLocations.Contains($Location) } -Function deployEngineApplication { - $azureSvcMgmtApiPermissionsScope = "user_impersonation" - $azureSvcMgmtAppId ="797f4846-ba00-4fd7-ba43-dac1f8f63013" - $msGraphApiPermissionsScope = "offline_access openid profile User.Read" - $msGraphAppId = "00000003-0000-0000-c000-000000000000" - $engineResourceAccess = [System.Collections.ArrayList]@( +Function Deploy-IPAMApplications { + Param( + [Parameter(Mandatory=$true)] + [string]$EngineAppName, + [Parameter(Mandatory=$true)] + [string]$UIAppName + ) + + $uiResourceAccess = [System.Collections.ArrayList]@( + @{ + ResourceAppId = "00000003-0000-0000-c000-000000000000"; + ResourceAccess = @( @{ - ResourceAppId = "00000003-0000-0000-c000-000000000000"; - ResourceAccess = @( - @{ - Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; - Type = "Scope" - }, - @{ - Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; - Type = "Scope" - }, - @{ - Id = "37f7f235-527c-4136-accd-4a02d197296e"; - Type = "Scope" - }, - @{ - Id = "14dad69e-099b-42c9-810b-d002981feec1"; - Type = "Scope" - } - ) + Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; + Type = "Scope" }, @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"; - ResourceAccess = @( - @{ - Id = "41094075-9dad-400e-a0bd-54e686782033"; - Type = "Scope" - } - ) + Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; + Type = "Scope" + }, + @{ + Id = "37f7f235-527c-4136-accd-4a02d197296e"; + Type = "Scope" } - ) + ) + } + ) + + $uiWebSettings = @{ + ImplicitGrantSetting = @{ + EnableAccessTokenIssuance = $true + EnableIdTokenIssuance = $true + } + } - # Create IPAM engine application - Write-Host "INFO: Creating Azure IPAM Engine Service Principal" -ForegroundColor green - Write-Verbose -Message "Creating Azure IPAM Engine Service Principal" - $global:engineApp = New-AzADApplication ` - -DisplayName "ipam-engine-app" ` + # Create IPAM UI Application + Write-Host "INFO: Creating Azure IPAM UI Application" -ForegroundColor green + Write-Verbose -Message "Creating Azure IPAM UI Application" + $uiApp = New-AzADApplication ` + -DisplayName $UiAppName ` + -SPARedirectUri "https://replace-this-value.azurewebsites.net" ` + -Web $uiWebSettings + + $engineResourceAccess = [System.Collections.ArrayList]@( + @{ + ResourceAppId = "00000003-0000-0000-c000-000000000000"; + ResourceAccess = @( + @{ + Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; + Type = "Scope" + }, + @{ + Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; + Type = "Scope" + }, + @{ + Id = "37f7f235-527c-4136-accd-4a02d197296e"; + Type = "Scope" + }, + @{ + Id = "14dad69e-099b-42c9-810b-d002981feec1"; + Type = "Scope" + } + ) + }, + @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"; + ResourceAccess = @( + @{ + Id = "41094075-9dad-400e-a0bd-54e686782033"; + Type = "Scope" + } + ) + } +) + +$engineApiGuid = New-Guid + +$engineApiSettings = @{ + KnownClientApplication = @( + $uiApp.AppId + ) + Oauth2PermissionScope = @( + @{ + AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." + AdminConsentDisplayName = "Access IPAM Engine API" + Id = $engineApiGuid + IsEnabled = $true + Type = "User" + UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." + UserConsentDisplayName = "Access IPAM Engine API" + Value = "access_as_user" + } + ) + RequestedAccessTokenVersion = 2 +} + + # Create IPAM Engine Application + Write-Host "INFO: Creating Azure IPAM Engine Application" -ForegroundColor green + Write-Verbose -Message "Creating Azure IPAM Engine Application" + $engineApp = New-AzADApplication ` + -DisplayName $EngineAppName ` + -Api $engineApiSettings ` -RequiredResourceAccess $engineResourceAccess - # Update IPAM engine application with API endpoint - Update-AzADApplication -ApplicationId $global:engineApp.AppId -IdentifierUri "api://$($global:engineApp.AppId)" - - # Create IPAM engine service principal - $engineSpn = New-AzADServicePrincipal ` - -ApplicationObject $global:engineApp ` - -Role "Reader" ` - -Scope "/providers/Microsoft.Management/managementGroups/$($tenantId)" + # Update IPAM Engine API endpoint + Write-Host "INFO: Updating Azure IPAM Engine API Endpoint" -ForegroundColor green + Write-Verbose -Message "Updating Azure IPAM UI Engine API Endpoint" + Update-AzADApplication -ApplicationId $engineApp.AppId -IdentifierUri "api://$($engineApp.AppId)" - # Create IPAM engine service principal credential - $global:engineSecret = New-AzADAppCredential -ApplicationObject $global:engineApp -StartDate (Get-Date) -EndDate (Get-Date).AddYears(2) - - Write-Host "INFO: Azure IPAM Engine Application & Service Principal created successfully" -ForegroundColor green - Write-Verbose -Message "Azure IPAM Engine Application & Service Principal created successfully" - - # Instantiate Microsoft Graph service principal object - $msGraphSpn = Get-AzADServicePrincipal ` - -ApplicationId $msGraphAppId - - # Instantiate Azure Service Management service principal object - $azureSvcMgmtSpn = Get-AzADServicePrincipal ` - -ApplicationId $azureSvcMgmtAppId - - # Grant admin consent for Microsoft Graph API permissions assigned to IPAM engine application - Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM Engine Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Microsoft Graph API permissions assigned to IPAM Engine Application" - New-MgOauth2PermissionGrant ` - -ResourceId $msGraphSpn.Id ` - -Scope $msGraphApiPermissionsScope ` - -ClientId $engineSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null - - Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor green - Write-Verbose -Message "Admin consent for Microsoft Graph API permissions granted successfully" - - # Grant admin consent for Azure Service Management API permissions assigned to IPAM application - Write-Host "INFO: Granting admin consent for Azure Service Management API permissions assigned to IPAM ENgine Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine Application" - New-MgOauth2PermissionGrant ` - -ResourceId $azureSvcMgmtSpn.Id ` - -Scope $azureSvcMgmtApiPermissionsScope ` - -ClientId $engineSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null - - Write-Host "INFO: Admin consent for Azure Service Management API permissions granted successfully" -ForegroundColor green - Write-Verbose -Message "Admin consent for Azure Service Management API API permissions granted successfully" - -} - -Function deployUiApplication { - $engineApiPermissionsScope = "access_as_user" - $msGraphAppId = "00000003-0000-0000-c000-000000000000" - $msGraphApiPermissionsScope = "Directory.Read.All openid User.Read" - $uiResourceAccess = [System.Collections.ArrayList]@( - @{ - ResourceAppId = "00000003-0000-0000-c000-000000000000"; - ResourceAccess = @( - @{ - Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; - Type = "Scope" - }, - @{ - Id = "37f7f235-527c-4136-accd-4a02d197296e"; - Type = "Scope" - }, - @{ - Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; - Type = "Scope" - } - ) - }, - @{ - ResourceAppId = $global:engineApp.AppId; - ResourceAccess = @( - @{ - Id = $engineApiGuid; - Type = "Scope" - } - ) - } + $uiEngineApiAccess =@{ + ResourceAppId = $engineApp.AppId + ResourceAccess = @( + @{ + Id = $engineApiGuid + Type = "Scope" + } ) - $uiWebSettings = @{ - ImplicitGrantSetting = @{ - EnableAccessTokenIssuance = $true - EnableIdTokenIssuance = $true - } - } + } - # Create IPAM UI Application - Write-Host "INFO: Creating Azure IPAM UI Application" -ForegroundColor green - Write-Verbose -Message "Creating Azure IPAM UI Application" - $global:uiApp = New-AzADApplication ` - -DisplayName "ipam-ui-app" ` - -Web $uiWebSettings ` - -RequiredResourceAccess $uiResourceAccess + $uiResourceAccess.Add($uiEngineApiAccess) | Out-Null - # Create IPAM UI service principal - $uiSpn = New-AzADServicePrincipal -ApplicationObject $global:uiApp + # Update IPAM UI Application Resource Access + Write-Host "INFO: Updating Azure IPAM UI Application Resource Access" -ForegroundColor green + Write-Verbose -Message "Updating Azure IPAM UI Application Resource Access" + Update-AzADApplication -ApplicationId $uiApp.AppId -RequiredResourceAccess $uiResourceAccess + + $uiObject = Get-AzADApplication -ApplicationId $uiApp.AppId + $engineObject = Get-AzADApplication -ApplicationId $engineApp.AppId + + # Create IPAM UI Service Principal + Write-Host "INFO: Creating Azure IPAM UI Service Principal" -ForegroundColor green + Write-Verbose -Message "Creating Azure IPAM UI Service Principal" + New-AzADServicePrincipal -ApplicationObject $uiObject | Out-Null - Write-Host "INFO: Azure IPAM UI Application & Service Principal created successfully" -ForegroundColor green - Write-Verbose -Message "Azure IPAM UI Application & Service Principal created successfully" - - # Instantiate Microsoft Graph service principal object - $msGraphSpn = Get-AzADServicePrincipal ` - -ApplicationId $msGraphAppId + # Create IPAM Engine Service Principal + Write-Host "INFO: Creating Azure IPAM Engine Service Principal" -ForegroundColor green + Write-Verbose -Message "Creating Azure IPAM Engine Service Principal" + New-AzADServicePrincipal -ApplicationObject $engineObject ` + -Role "Reader" ` + -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" ` + | Out-Null - # Instantiate Azure IPAM engine service principal object - $engineSpn = Get-AzADServicePrincipal ` - -ApplicationId $global:engineApp.AppId + # Create IPAM Engine Secret + Write-Host "INFO: Creating Azure IPAM Engine Secret" -ForegroundColor green + Write-Verbose -Message "Creating Azure IPAM Engine Secret" + $engineSecret = New-AzADAppCredential -ApplicationObject $engineObject -StartDate (Get-Date) -EndDate (Get-Date).AddYears(2) - # Grant admin consent for Microsoft Graph API permissions assigned to IPAM UI application - Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI Application" - New-MgOauth2PermissionGrant ` - -ResourceId $msGraphSpn.Id ` - -Scope $msGraphApiPermissionsScope ` - -ClientId $uiSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null + Write-Host "INFO: Azure IPAM Engine Application & Service Principal created successfully" -ForegroundColor green + Write-Verbose -Message "Azure IPAM Engine Application & Service Principal created successfully" - Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor green - Write-Verbose -Message "Admin consent for Microsoft Graph API permissions granted successfully" - - # Grant admin consent for Azure Service Management API permissions assigned to IPAM application - Write-Host "INFO: Granting admin consent for Azure IPAM Engine API permissions assigned to IPAM UI Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Azure IPAM Engine API permissions assigned to IPAM UI Application" - New-MgOauth2PermissionGrant ` - -ResourceId $engineSpn.Id ` - -Scope $engineApiPermissionsScope ` - -ClientId $uiSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null - - Write-Host "INFO: Admin consent for Azure IPAM Engine API permissions granted successfully" -ForegroundColor green - Write-Verbose -Message "Admin consent for Azure IPAM Engine API API permissions granted successfully" + $appDetails = @{ + UIAppId = $uiApp.AppId + EngineAppId = $engineApp.AppId + EngineSecret = $engineSecret.SecretText + } + return $appDetails } -Function updateEngineApplication { - $engineApiSettings = @{ - KnownClientApplication = @( - $global:uiApp.AppId - ) - Oauth2PermissionScope = @( - @{ - AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." - AdminConsentDisplayName = "Access IPAM Engine API" - Id = $engineApiGuid - IsEnabled = $true - Type = "User" - UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." - UserConsentDisplayName = "Access IPAM Engine API" - Value = "access_as_user" - } - ) - RequestedAccessTokenVersion = 2 - } +Function Deploy-Bicep { + Param( + [Parameter(Mandatory=$true)] + [string]$UIAppId, + [Parameter(Mandatory=$true)] + [string]$EngineAppId, + [Parameter(Mandatory=$true)] + [string]$EngineSecret, + [Parameter(Mandatory=$false)] + [string]$NamePrefix, + [Parameter(Mandatory=$false)] + [hashtable]$Tags + ) - # Update IPAM engine application API settings - Write-Host "INFO: Updating Azure IPAM Engine Application" -ForegroundColor green - Write-Verbose -Message "Updating Azure IPAM Engine Application" - Update-AzADApplication -ApplicationId $global:engineApp.AppId -Api $engineApiSettings - - - Write-Host "INFO: Updated Azure IPAM Engine Application successfully" -ForegroundColor green - Write-Verbose -Message "Updated Azure IPAM Engine Application successfully" - -} - -Function deployBicep { - Write-Host "INFO: Deploying IPAM bicep templates" -ForegroundColor green - Write-Verbose -Message "Deploying bicep templates" + Write-Host "INFO: Deploying IPAM bicep templates" -ForegroundColor green + Write-Verbose -Message "Deploying bicep templates" # Instantiate deployment parameter object - $deploymentParameters = @{ - 'engineAppId' = $global:engineApp.AppId - 'engineAppSecret' = $global:engineSecret.SecretText - 'namePrefix' = $namePrefix - 'uiAppId' = $global:uiApp.AppId - } + $deploymentParameters = @{ + engineAppId = $EngineAppId + engineAppSecret = $EngineSecret + uiAppId = $UiAppId + } + + if($NamePrefix) { + $deploymentParameters.Add('namePrefix', $NamePrefix) + } + + if($Tags) { + # $tagsParameter = $tags | ConvertFrom-Json -AsHashtable + $deploymentParameters.Add('tags', $Tags) + } - $tagsParameter = $tags | ConvertFrom-Json -AsHashtable - - $deploymentParameters.Add('tags',$tagsParameter) - # Deploy IPAM bicep template - $global:deployment = New-AzSubscriptionDeployment ` - -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` - -Location $location ` - -TemplateFile main.bicep ` - -TemplateParameterObject $deploymentParameters + $deployment = New-AzSubscriptionDeployment ` + -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` + -Location $location ` + -TemplateFile main.bicep ` + -TemplateParameterObject $deploymentParameters Write-Host "INFO: IPAM bicep templates deployed successfully" -ForegroundColor green Write-Verbose -Message "IPAM bicep template deployed successfully" - + + return $deployment } -Function updateUiApplication { - Write-Host "INFO: Updating UI Application with SPA configuration" -ForegroundColor green - Write-Verbose -Message "Updating UI Application with SPA configuration" - - $uiAppId = $global:deployment.Parameters["uiAppId"].Value - $appServiceEndpoint = "https://$($global:deployment.Outputs["appServiceHostName"].Value)" - +Function Update-UIApplication { + Param( + [Parameter(Mandatory=$true)] + [string]$UIAppId, + [Parameter(Mandatory=$true)] + [string]$Endpoint + ) + + Write-Host "INFO: Updating UI Application with SPA configuration" -ForegroundColor green + Write-Verbose -Message "Updating UI Application with SPA configuration" + + $appServiceEndpoint = "https://$Endpoint" + # Update UI Application with single-page application configuration - Update-AzADApplication -ApplicationId $uiAppId -SPARedirectUri $appServiceEndpoint + Update-AzADApplication -ApplicationId $UIAppId -SPARedirectUri $appServiceEndpoint + + Write-Host "INFO: UI Application SPA configuration update complete" -ForegroundColor green + Write-Verbose -Message "UI Application SPA configuration update complete" +} + +Function Grant-AdminConsent { + Param( + [Parameter(Mandatory=$true)] + [string]$UIAppId, + [Parameter(Mandatory=$true)] + [string]$EngineAppId + ) + + $uiGraphScopes = [System.Collections.ArrayList]@( + @{ + scopeId = "00000003-0000-0000-c000-000000000000" + scopes = "openid User.Read Directory.Read.All" + } + ) - Write-Host "INFO: UI Application SPA configuration update complete" -ForegroundColor green - Write-Verbose -Message "UI Application SPA configuration update complete" + $engineGraphScopes = [System.Collections.ArrayList]@( + @{ + scopeId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" + scopes = "user_impersonation" + } + @{ + scopeId = "00000003-0000-0000-c000-000000000000" + scopes = "offline_access openid profile User.Read" + } + ) + + # Get Microsoft Graph Access Token + $accesstoken = (Get-AzAccessToken -Resource "https://graph.microsoft.com/").Token + + # Connect to Microsoft Graph + Write-Host "INFO: Logging in to Microsoft Graph" -ForegroundColor green + Write-Verbose -Message "Logging in to Microsoft Graph" + Connect-MgGraph -AccessToken $accesstoken | Out-Null + + # Fetch Azure IPAM UI Service Principal + $uiSpn = Get-AzADServicePrincipal ` + -ApplicationId $UIAppId + + # Fetch Azure IPAM Engine Service Principal + $engineSpn = Get-AzADServicePrincipal ` + -ApplicationId $EngineAppId + + # Grant admin consent for Microsoft Graph API permissions assigned to IPAM UI application + Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI Application" -ForegroundColor Green + Write-Verbose -Message "Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI Application" + foreach($scope in $uiGraphScopes) { + $msGraphId = Get-AzADServicePrincipal ` + -ApplicationId $scope.scopeId + New-MgOauth2PermissionGrant ` + -ResourceId $msGraphId.Id ` + -Scope $scope.scopes ` + -ClientId $uiSpn.Id ` + -ConsentType AllPrincipals ` + | Out-Null + } + + Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor green + Write-Verbose -Message "Admin consent for Microsoft Graph API permissions granted successfully" + + # Grant admin consent for Azure Service Management API permissions assigned to IPAM application + Write-Host "INFO: Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine Application" -ForegroundColor Green + Write-Verbose -Message "Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine Application" + New-MgOauth2PermissionGrant ` + -ResourceId $engineSpn.Id ` + -Scope "access_as_user" ` + -ClientId $uiSpn.Id ` + -ConsentType AllPrincipals ` + | Out-Null + + Write-Host "INFO: Admin consent for Azure Service Management API permissions granted successfully" -ForegroundColor green + Write-Verbose -Message "Admin consent for Azure Service Management API API permissions granted successfully" + + # Grant admin consent for Microsoft Graph API permissions assigned to IPAM engine application + Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM Engine Application" -ForegroundColor Green + Write-Verbose -Message "Granting admin consent for Microsoft Graph API permissions assigned to IPAM Engine Application" + foreach($scope in $engineGraphScopes) { + $msGraphId = Get-AzADServicePrincipal ` + -ApplicationId $scope.scopeId + + New-MgOauth2PermissionGrant ` + -ResourceId $msGraphId.Id ` + -Scope $scope.scopes ` + -ClientId $engineSpn.Id ` + -ConsentType AllPrincipals ` + | Out-Null + } + + Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor green + Write-Verbose -Message "Admin consent for Microsoft Graph API permissions granted successfully" } try { - # Connect to Microsoft Graph - $accesstoken = (Get-AzAccessToken -Resource "https://graph.microsoft.com/").Token + # Validate Azure Region + Write-Host "INFO: Validating Azure Region selected for deployment" -ForegroundColor green + Write-Verbose -Message "Validating Azure Region selected for deployment" - Connect-MgGraph -AccessToken $accesstoken + if (Test-Location -Location $Location) { + Write-Host "INFO: Azure Region validated successfully" -ForegroundColor green + Write-Verbose -Message "Azure Region validated successfully" + } else { + Write-Host "ERROR: Location provided is not a valid Azure Region!" -ForegroundColor red + exit + } - validateLocation $location + $appDetails = Deploy-IPAMApplications ` + -UIAppName $UIAppName ` + -EngineAppName $EngineAppName - deployEngineApplication + Grant-AdminConsent ` + -UIAppId $appDetails.UIAppId ` + -EngineAppId $appDetails.EngineAppId - deployUiApplication + $deployment = Deploy-Bicep @appDetails - updateEngineApplication - - deployBicep - - updateUiApplication + Update-UIApplication ` + -UIAppId $appDetails.UIAppId ` + -Endpoint $deployment.Outputs["appServiceHostName"].Value Write-Host "INFO: Azure IPAM Solution deployed successfully" -ForegroundColor green Write-Verbose -Message "Azure IPAM Solution deployed successfully" - } catch { - $_ | Out-File -FilePath $logFile -Append - Write-Host "ERROR: Unable to deploy Azure IPAM solution due to an exception, see $logFile for detailed information!" -ForegroundColor red - exit - + $_ | Out-File -FilePath $logFile -Append + Write-Host "ERROR: Unable to deploy Azure IPAM solution due to an exception, see $logFile for detailed information!" -ForegroundColor red + exit } diff --git a/deploy/main.bicep b/deploy/main.bicep index b8a23f8..4fddefb 100644 --- a/deploy/main.bicep +++ b/deploy/main.bicep @@ -8,7 +8,7 @@ param guid string = newGuid() param location string = deployment().location @description('Prefix for Resource Naming') -param namePrefix string +param namePrefix string = 'ipam' @description('IPAM-UI App Registration Client/App ID') param uiAppId string @@ -21,7 +21,7 @@ param engineAppId string param engineAppSecret string @description('Tags') -param tags object +param tags object = {} // Resource naming variables var appServiceName = '${namePrefix}-${uniqueString(guid)}' diff --git a/engine/app/routers/user.py b/engine/app/routers/user.py index 90675f8..fd7af05 100644 --- a/engine/app/routers/user.py +++ b/engine/app/routers/user.py @@ -140,7 +140,10 @@ async def get_user( admins = await cosmos_query("admins") - is_admin = next((x for x in admins['admins'] if x['id'] == target_user['id']), None) + if admins['admins']: + is_admin = next((x for x in admins['admins'] if x['id'] == target_user['id']), None) + else: + is_admin = True target_user['isAdmin'] = True if is_admin else False diff --git a/ui/src/features/login/Login.js b/ui/src/features/login/Login.js index 8daa859..7aae7c5 100644 --- a/ui/src/features/login/Login.js +++ b/ui/src/features/login/Login.js @@ -23,6 +23,15 @@ const Login = () => { } }, [isAuthenticated, inProgress, instance]); + // React.useEffect(() => { + // instance.loginRedirect(loginRequest).catch((e) => { + // console.log("LOGIN ERROR:"); + // console.log("--------------"); + // console.error(e); + // console.log("--------------"); + // }); + // }, []); + return(null) }; From e069a51ca2f56900158531d115cb3ba51790cebb Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sat, 28 May 2022 09:58:38 -0700 Subject: [PATCH 2/9] Additional work towards single multi-purpose script --- deploy/deploy.ps1 | 430 +++++++++++++++++++++++++++++++--------------- 1 file changed, 290 insertions(+), 140 deletions(-) diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index a502e25..c7bdb35 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -10,23 +10,87 @@ #Requires -Modules @{ ModuleName="Microsoft.Graph"; ModuleVersion="1.9.6"} # Intake and set global parameters -Param( - [Parameter(Mandatory = $false)] - [string]$Location = "westus3", - [Parameter(Mandatory = $false)] - [string]$UIAppName = "ipam-ui-app", - [Parameter(Mandatory = $false)] - [string]$EngineAppName = "ipam-engine-app", - [Parameter(Mandatory = $false)] - [string]$NamePrefix = "ipam", - [Parameter(Mandatory = $false)] - [hashtable]$Tags +[CmdletBinding(DefaultParameterSetName = 'Full')] +param( + [Parameter(Mandatory = $true, + ParameterSetName = 'Full')] + [Parameter(Mandatory = $true, + ParameterSetName = 'TemplateOnly')] + [string] + $Location, + + [Parameter(Mandatory = $false, + ParameterSetName = 'Full')] + [Parameter(Mandatory = $false, + ParameterSetName = 'AppsOnly')] + [string] + $UIAppName, + + [Parameter(Mandatory = $false, + ParameterSetName = 'Full')] + [Parameter(Mandatory = $false, + ParameterSetName = 'AppsOnly')] + [string] + $EngineAppName, + + [Parameter(Mandatory = $false, + ParameterSetName = 'Full')] + [Parameter(Mandatory = $false, + ParameterSetName = 'TemplateOnly')] + [string] + $NamePRefix, + + [Parameter(Mandatory = $false, + ParameterSetName = 'Full')] + [Parameter(Mandatory = $false, + ParameterSetName = 'TemplateOnly')] + [hashtable] + $Tags, + + [Parameter(Mandatory = $false, + ParameterSetName = 'TemplateOnly')] + [switch] + $TemplateOnly, + + [Parameter(Mandatory = $false, + ParameterSetName = 'AppsOnly')] + [switch] + $AppsOnly, + + [Parameter(Mandatory = $true, + ParameterSetName = 'TemplateOnly')] + [ValidateScript({ + if(-Not ($_ | Test-Path) ){ + throw "File or does not exist." + } + if(-Not ($_ | Test-Path -PathType Leaf) ){ + throw "The ParameterFile argument must be a file, folder paths are not allowed." + } + if($_ -notmatch "(\.json)"){ + throw "The file specified in the ParameterFile argument must be of type json." + } + return $true + })] + [System.IO.FileInfo]$ParameterFile ) +# Intake and set global parameters +# Param( +# [Parameter(Mandatory = $false)] +# [string]$Location = "westus3", +# [Parameter(Mandatory = $false)] +# [string]$UIAppName = "ipam-ui-app", +# [Parameter(Mandatory = $false)] +# [string]$EngineAppName = "ipam-engine-app", +# [Parameter(Mandatory = $false)] +# [string]$NamePrefix, +# [Parameter(Mandatory = $false)] +# [hashtable]$Tags +# ) + $Env:SuppressAzurePowerShellBreakingChangeWarnings = $true $logFile = "./deploy_$(get-date -format `"yyyyMMddhhmmsstt`").log" -$tenantId = (Get-AzContext).Tenant.Id # Set preference variables $ErrorActionPreference = "Stop" @@ -37,8 +101,8 @@ Function Test-Location { [string]$Location ) - $validLocations = Get-AzLocation | Select-Object -ExpandProperty Location - return $validLocations.Contains($Location) + $validLocations = Get-AzLocation | Select-Object -ExpandProperty Location + return $validLocations.Contains($Location) } Function Deploy-IPAMApplications { @@ -46,9 +110,11 @@ Function Deploy-IPAMApplications { [Parameter(Mandatory=$true)] [string]$EngineAppName, [Parameter(Mandatory=$true)] - [string]$UIAppName + [string]$UIAppName, + [Parameter(Mandatory=$true)] + [string]$TenantId ) - + $uiResourceAccess = [System.Collections.ArrayList]@( @{ ResourceAppId = "00000003-0000-0000-c000-000000000000"; @@ -77,8 +143,8 @@ Function Deploy-IPAMApplications { } # Create IPAM UI Application - Write-Host "INFO: Creating Azure IPAM UI Application" -ForegroundColor green - Write-Verbose -Message "Creating Azure IPAM UI Application" + Write-Host "INFO: Creating Azure IPAM UI Application" -ForegroundColor green + Write-Verbose -Message "Creating Azure IPAM UI Application" $uiApp = New-AzADApplication ` -DisplayName $UiAppName ` -SPARedirectUri "https://replace-this-value.azurewebsites.net" ` @@ -138,9 +204,9 @@ $engineApiSettings = @{ RequestedAccessTokenVersion = 2 } - # Create IPAM Engine Application - Write-Host "INFO: Creating Azure IPAM Engine Application" -ForegroundColor green - Write-Verbose -Message "Creating Azure IPAM Engine Application" + # Create IPAM Engine Application + Write-Host "INFO: Creating Azure IPAM Engine Application" -ForegroundColor green + Write-Verbose -Message "Creating Azure IPAM Engine Application" $engineApp = New-AzADApplication ` -DisplayName $EngineAppName ` -Api $engineApiSettings ` @@ -148,7 +214,7 @@ $engineApiSettings = @{ # Update IPAM Engine API endpoint Write-Host "INFO: Updating Azure IPAM Engine API Endpoint" -ForegroundColor green - Write-Verbose -Message "Updating Azure IPAM UI Engine API Endpoint" + Write-Verbose -Message "Updating Azure IPAM UI Engine API Endpoint" Update-AzADApplication -ApplicationId $engineApp.AppId -IdentifierUri "api://$($engineApp.AppId)" $uiEngineApiAccess =@{ @@ -164,33 +230,33 @@ $engineApiSettings = @{ $uiResourceAccess.Add($uiEngineApiAccess) | Out-Null # Update IPAM UI Application Resource Access - Write-Host "INFO: Updating Azure IPAM UI Application Resource Access" -ForegroundColor green - Write-Verbose -Message "Updating Azure IPAM UI Application Resource Access" + Write-Host "INFO: Updating Azure IPAM UI Application Resource Access" -ForegroundColor green + Write-Verbose -Message "Updating Azure IPAM UI Application Resource Access" Update-AzADApplication -ApplicationId $uiApp.AppId -RequiredResourceAccess $uiResourceAccess $uiObject = Get-AzADApplication -ApplicationId $uiApp.AppId $engineObject = Get-AzADApplication -ApplicationId $engineApp.AppId # Create IPAM UI Service Principal - Write-Host "INFO: Creating Azure IPAM UI Service Principal" -ForegroundColor green - Write-Verbose -Message "Creating Azure IPAM UI Service Principal" + Write-Host "INFO: Creating Azure IPAM UI Service Principal" -ForegroundColor green + Write-Verbose -Message "Creating Azure IPAM UI Service Principal" New-AzADServicePrincipal -ApplicationObject $uiObject | Out-Null # Create IPAM Engine Service Principal - Write-Host "INFO: Creating Azure IPAM Engine Service Principal" -ForegroundColor green - Write-Verbose -Message "Creating Azure IPAM Engine Service Principal" + Write-Host "INFO: Creating Azure IPAM Engine Service Principal" -ForegroundColor green + Write-Verbose -Message "Creating Azure IPAM Engine Service Principal" New-AzADServicePrincipal -ApplicationObject $engineObject ` -Role "Reader" ` - -Scope "/providers/Microsoft.Management/managementGroups/$tenantId" ` + -Scope "/providers/Microsoft.Management/managementGroups/$TenantId" ` | Out-Null # Create IPAM Engine Secret - Write-Host "INFO: Creating Azure IPAM Engine Secret" -ForegroundColor green - Write-Verbose -Message "Creating Azure IPAM Engine Secret" + Write-Host "INFO: Creating Azure IPAM Engine Secret" -ForegroundColor green + Write-Verbose -Message "Creating Azure IPAM Engine Secret" $engineSecret = New-AzADAppCredential -ApplicationObject $engineObject -StartDate (Get-Date) -EndDate (Get-Date).AddYears(2) - Write-Host "INFO: Azure IPAM Engine Application & Service Principal created successfully" -ForegroundColor green - Write-Verbose -Message "Azure IPAM Engine Application & Service Principal created successfully" + Write-Host "INFO: Azure IPAM Engine Application & Service Principal created successfully" -ForegroundColor green + Write-Verbose -Message "Azure IPAM Engine Application & Service Principal created successfully" $appDetails = @{ UIAppId = $uiApp.AppId @@ -201,72 +267,6 @@ $engineApiSettings = @{ return $appDetails } -Function Deploy-Bicep { - Param( - [Parameter(Mandatory=$true)] - [string]$UIAppId, - [Parameter(Mandatory=$true)] - [string]$EngineAppId, - [Parameter(Mandatory=$true)] - [string]$EngineSecret, - [Parameter(Mandatory=$false)] - [string]$NamePrefix, - [Parameter(Mandatory=$false)] - [hashtable]$Tags - ) - - Write-Host "INFO: Deploying IPAM bicep templates" -ForegroundColor green - Write-Verbose -Message "Deploying bicep templates" - - # Instantiate deployment parameter object - $deploymentParameters = @{ - engineAppId = $EngineAppId - engineAppSecret = $EngineSecret - uiAppId = $UiAppId - } - - if($NamePrefix) { - $deploymentParameters.Add('namePrefix', $NamePrefix) - } - - if($Tags) { - # $tagsParameter = $tags | ConvertFrom-Json -AsHashtable - $deploymentParameters.Add('tags', $Tags) - } - - # Deploy IPAM bicep template - $deployment = New-AzSubscriptionDeployment ` - -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` - -Location $location ` - -TemplateFile main.bicep ` - -TemplateParameterObject $deploymentParameters - - Write-Host "INFO: IPAM bicep templates deployed successfully" -ForegroundColor green - Write-Verbose -Message "IPAM bicep template deployed successfully" - - return $deployment -} - -Function Update-UIApplication { - Param( - [Parameter(Mandatory=$true)] - [string]$UIAppId, - [Parameter(Mandatory=$true)] - [string]$Endpoint - ) - - Write-Host "INFO: Updating UI Application with SPA configuration" -ForegroundColor green - Write-Verbose -Message "Updating UI Application with SPA configuration" - - $appServiceEndpoint = "https://$Endpoint" - - # Update UI Application with single-page application configuration - Update-AzADApplication -ApplicationId $UIAppId -SPARedirectUri $appServiceEndpoint - - Write-Host "INFO: UI Application SPA configuration update complete" -ForegroundColor green - Write-Verbose -Message "UI Application SPA configuration update complete" -} - Function Grant-AdminConsent { Param( [Parameter(Mandatory=$true)] @@ -294,12 +294,12 @@ Function Grant-AdminConsent { ) # Get Microsoft Graph Access Token - $accesstoken = (Get-AzAccessToken -Resource "https://graph.microsoft.com/").Token + $accesstoken = (Get-AzAccessToken -Resource "https://graph.microsoft.com/").Token # Connect to Microsoft Graph - Write-Host "INFO: Logging in to Microsoft Graph" -ForegroundColor green - Write-Verbose -Message "Logging in to Microsoft Graph" - Connect-MgGraph -AccessToken $accesstoken | Out-Null + Write-Host "INFO: Logging in to Microsoft Graph" -ForegroundColor green + Write-Verbose -Message "Logging in to Microsoft Graph" + Connect-MgGraph -AccessToken $accesstoken | Out-Null # Fetch Azure IPAM UI Service Principal $uiSpn = Get-AzADServicePrincipal ` @@ -309,9 +309,9 @@ Function Grant-AdminConsent { $engineSpn = Get-AzADServicePrincipal ` -ApplicationId $EngineAppId - # Grant admin consent for Microsoft Graph API permissions assigned to IPAM UI application - Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI Application" + # Grant admin consent for Microsoft Graph API permissions assigned to IPAM UI application + Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI Application" -ForegroundColor Green + Write-Verbose -Message "Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI Application" foreach($scope in $uiGraphScopes) { $msGraphId = Get-AzADServicePrincipal ` -ApplicationId $scope.scopeId @@ -324,12 +324,12 @@ Function Grant-AdminConsent { | Out-Null } - Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor green - Write-Verbose -Message "Admin consent for Microsoft Graph API permissions granted successfully" + Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor green + Write-Verbose -Message "Admin consent for Microsoft Graph API permissions granted successfully" - # Grant admin consent for Azure Service Management API permissions assigned to IPAM application - Write-Host "INFO: Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine Application" + # Grant admin consent for Azure Service Management API permissions assigned to IPAM application + Write-Host "INFO: Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine Application" -ForegroundColor Green + Write-Verbose -Message "Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine Application" New-MgOauth2PermissionGrant ` -ResourceId $engineSpn.Id ` -Scope "access_as_user" ` @@ -341,8 +341,8 @@ Function Grant-AdminConsent { Write-Verbose -Message "Admin consent for Azure Service Management API API permissions granted successfully" # Grant admin consent for Microsoft Graph API permissions assigned to IPAM engine application - Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM Engine Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Microsoft Graph API permissions assigned to IPAM Engine Application" + Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM Engine Application" -ForegroundColor Green + Write-Verbose -Message "Granting admin consent for Microsoft Graph API permissions assigned to IPAM Engine Application" foreach($scope in $engineGraphScopes) { $msGraphId = Get-AzADServicePrincipal ` -ApplicationId $scope.scopeId @@ -355,42 +355,192 @@ Function Grant-AdminConsent { | Out-Null } - Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor green - Write-Verbose -Message "Admin consent for Microsoft Graph API permissions granted successfully" + Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor green + Write-Verbose -Message "Admin consent for Microsoft Graph API permissions granted successfully" +} + +Function Save-Parameters { + Param( + [Parameter(Mandatory=$true)] + [string]$UIAppId, + [Parameter(Mandatory=$true)] + [string]$EngineAppId, + [Parameter(Mandatory=$true)] + [string]$EngineSecret + ) + + Write-Host "INFO: Populating Bicep parameter file for infrastructure deployment" -ForegroundColor Green + Write-Verbose -Message "Populating Bicep parameter file for infrastructure deployment" + + # Retrieve JSON object from sample parameter file + $parametersObject = Get-Content main.parameters.example.json | ConvertFrom-Json + + # update parameter values + $parametersObject.parameters.uiAppId.value = $UIAppId + $parametersObject.parameters.engineAppId.value = $EngineAppId + $parametersObject.parameters.engineAppSecret.value = $EngineSecret + $parametersObject.parameters = $parametersObject.parameters | Select-Object * -ExcludeProperty namePrefix, tags + + # Output updated parameter file for Bicep deployment + $parametersObject | ConvertTo-Json -Depth 4 | Out-File -FilePath main.parameters.json + + Write-Host "INFO: Bicep parameter file populated successfully" -ForegroundColor green + Write-Verbose -Message "Bicep parameter file populated successfully" +} + +Function Import-Parameters { + Param( + [Parameter(Mandatory=$true)] + [System.IO.FileInfo]$ParameterFile + ) + + Write-Host "INFO: Importing values from Bicep parameters file" -ForegroundColor Green + Write-Verbose -Message "Importing values from Bicep parameters file" + + # Retrieve JSON object from sample parameter file + $parametersObject = Get-Content $ParameterFile | ConvertFrom-Json + + # update parameter values + $UIAppId = $parametersObject.parameters.uiAppId.value + $EngineAppId = $parametersObject.parameters.engineAppId.value + $EngineSecret = $parametersObject.parameters.engineAppSecret.value + + Write-Host "INFO: Successfully import Bicep parameter values" -ForegroundColor green + Write-Verbose -Message "Successfully import Bicep parameter values" + + $appDetails = @{ + UIAppId = $UIAppId + EngineAppId = $EngineAppId + EngineSecret = $EngineSecret + } + + return $appDetails +} + +Function Deploy-Bicep { + Param( + [Parameter(Mandatory=$true)] + [string]$UIAppId, + [Parameter(Mandatory=$true)] + [string]$EngineAppId, + [Parameter(Mandatory=$true)] + [string]$EngineSecret, + [Parameter(Mandatory=$false)] + [string]$NamePrefix, + [Parameter(Mandatory=$false)] + [hashtable]$Tags + ) + + Write-Host "INFO: Deploying IPAM bicep templates" -ForegroundColor green + Write-Verbose -Message "Deploying bicep templates" + + # Instantiate deployment parameter object + $deploymentParameters = @{ + engineAppId = $EngineAppId + engineAppSecret = $EngineSecret + uiAppId = $UiAppId + } + + if($NamePrefix) { + $deploymentParameters.Add('namePrefix', $NamePrefix) + } + + if($Tags) { + $deploymentParameters.Add('tags', $Tags) + } + + # Deploy IPAM bicep template + $deployment = New-AzSubscriptionDeployment ` + -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` + -Location $location ` + -TemplateFile main.bicep ` + -TemplateParameterObject $deploymentParameters + + Write-Host "INFO: IPAM bicep templates deployed successfully" -ForegroundColor green + Write-Verbose -Message "IPAM bicep template deployed successfully" + + return $deployment +} + +Function Update-UIApplication { + Param( + [Parameter(Mandatory=$true)] + [string]$UIAppId, + [Parameter(Mandatory=$true)] + [string]$Endpoint + ) + + Write-Host "INFO: Updating UI Application with SPA configuration" -ForegroundColor green + Write-Verbose -Message "Updating UI Application with SPA configuration" + + $appServiceEndpoint = "https://$Endpoint" + + # Update UI Application with single-page application configuration + Update-AzADApplication -ApplicationId $UIAppId -SPARedirectUri $appServiceEndpoint + + Write-Host "INFO: UI Application SPA configuration update complete" -ForegroundColor green + Write-Verbose -Message "UI Application SPA configuration update complete" } try { - # Validate Azure Region - Write-Host "INFO: Validating Azure Region selected for deployment" -ForegroundColor green - Write-Verbose -Message "Validating Azure Region selected for deployment" - - if (Test-Location -Location $Location) { - Write-Host "INFO: Azure Region validated successfully" -ForegroundColor green - Write-Verbose -Message "Azure Region validated successfully" - } else { - Write-Host "ERROR: Location provided is not a valid Azure Region!" -ForegroundColor red - exit + if ($PSCmdlet.ParameterSetName -in ('Full', 'AppsOnly')) { + # Fetch Tenant ID + Write-Host "INFO: Fetching Tenant ID from Azure PowerShell SDK" -ForegroundColor green + Write-Verbose -Message "Fetching Tenant ID from Azure PowerShell SDK" + $tenantId = (Get-AzContext).Tenant.Id } - $appDetails = Deploy-IPAMApplications ` - -UIAppName $UIAppName ` - -EngineAppName $EngineAppName + if ($PSCmdlet.ParameterSetName -in ('Full', 'TemplateOnly')) { + # Validate Azure Region + Write-Host "INFO: Validating Azure Region selected for deployment" -ForegroundColor green + Write-Verbose -Message "Validating Azure Region selected for deployment" - Grant-AdminConsent ` - -UIAppId $appDetails.UIAppId ` - -EngineAppId $appDetails.EngineAppId + if (Test-Location -Location $Location) { + Write-Host "INFO: Azure Region validated successfully" -ForegroundColor green + Write-Verbose -Message "Azure Region validated successfully" + } else { + Write-Host "ERROR: Location provided is not a valid Azure Region!" -ForegroundColor red + exit + } + } - $deployment = Deploy-Bicep @appDetails + if ($PSCmdlet.ParameterSetName -in ('Full', 'AppsOnly')) { + $appDetails = Deploy-IPAMApplications ` + -UIAppName $UIAppName ` + -EngineAppName $EngineAppName ` + -TenantId $tenantId - Update-UIApplication ` - -UIAppId $appDetails.UIAppId ` - -Endpoint $deployment.Outputs["appServiceHostName"].Value + Grant-AdminConsent ` + -UIAppId $appDetails.UIAppId ` + -EngineAppId $appDetails.EngineAppId + } - Write-Host "INFO: Azure IPAM Solution deployed successfully" -ForegroundColor green - Write-Verbose -Message "Azure IPAM Solution deployed successfully" + if ($PSCmdlet.ParameterSetName -eq 'AppsOnly') { + Save-Parameters @appDetails + } + + if ($PSCmdlet.ParameterSetName -eq 'TemplateOnly') { + $appDetails = Import-Parameters ` + -ParameterFile $ParameterFile + } + + if ($PSCmdlet.ParameterSetName -in ('Full', 'TemplateOnly')) { + $deployment = Deploy-Bicep @appDetails ` + -NamePrefix $NamePrefix ` + -Tags $Tags + } + + if ($PSCmdlet.ParameterSetName -eq 'Full') { + Update-UIApplication ` + -UIAppId $appDetails.UIAppId ` + -Endpoint $deployment.Outputs["appServiceHostName"].Value + } + + Write-Host "INFO: Azure IPAM Solution deployed successfully" -ForegroundColor green + Write-Verbose -Message "Azure IPAM Solution deployed successfully" } catch { - $_ | Out-File -FilePath $logFile -Append - Write-Host "ERROR: Unable to deploy Azure IPAM solution due to an exception, see $logFile for detailed information!" -ForegroundColor red - exit + $_ | Out-File -FilePath $logFile -Append + Write-Host "ERROR: Unable to deploy Azure IPAM solution due to an exception, see $logFile for detailed information!" -ForegroundColor red + exit } From b3bb16bde0a88bc72d79b0cd249088e7b8ce3880 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sat, 28 May 2022 10:12:32 -0700 Subject: [PATCH 3/9] Deploy script cleanup --- deploy/deploy.ps1 | 135 +++++++++++++++++++++------------------------- 1 file changed, 62 insertions(+), 73 deletions(-) diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index c7bdb35..3f675f3 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -74,27 +74,15 @@ param( [System.IO.FileInfo]$ParameterFile ) -# Intake and set global parameters -# Param( -# [Parameter(Mandatory = $false)] -# [string]$Location = "westus3", -# [Parameter(Mandatory = $false)] -# [string]$UIAppName = "ipam-ui-app", -# [Parameter(Mandatory = $false)] -# [string]$EngineAppName = "ipam-engine-app", -# [Parameter(Mandatory = $false)] -# [string]$NamePrefix, -# [Parameter(Mandatory = $false)] -# [hashtable]$Tags -# ) - -$Env:SuppressAzurePowerShellBreakingChangeWarnings = $true - -$logFile = "./deploy_$(get-date -format `"yyyyMMddhhmmsstt`").log" - # Set preference variables $ErrorActionPreference = "Stop" +# Hide Azure PowerShell SDK Warnings +$Env:SuppressAzurePowerShellBreakingChangeWarnings = $true + +# Set Log File Location +$logFile = "./deploy_$(get-date -format `"yyyyMMddhhmmsstt`").log" + Function Test-Location { Param( [Parameter(Mandatory=$true)] @@ -102,6 +90,7 @@ Function Test-Location { ) $validLocations = Get-AzLocation | Select-Object -ExpandProperty Location + return $validLocations.Contains($Location) } @@ -134,7 +123,7 @@ Function Deploy-IPAMApplications { ) } ) - + $uiWebSettings = @{ ImplicitGrantSetting = @{ EnableAccessTokenIssuance = $true @@ -151,58 +140,58 @@ Function Deploy-IPAMApplications { -Web $uiWebSettings $engineResourceAccess = [System.Collections.ArrayList]@( - @{ - ResourceAppId = "00000003-0000-0000-c000-000000000000"; - ResourceAccess = @( - @{ - Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; - Type = "Scope" - }, - @{ - Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; - Type = "Scope" - }, - @{ - Id = "37f7f235-527c-4136-accd-4a02d197296e"; - Type = "Scope" - }, - @{ - Id = "14dad69e-099b-42c9-810b-d002981feec1"; - Type = "Scope" - } - ) - }, - @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"; - ResourceAccess = @( - @{ - Id = "41094075-9dad-400e-a0bd-54e686782033"; - Type = "Scope" - } - ) - } -) - -$engineApiGuid = New-Guid - -$engineApiSettings = @{ - KnownClientApplication = @( - $uiApp.AppId - ) - Oauth2PermissionScope = @( - @{ - AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." - AdminConsentDisplayName = "Access IPAM Engine API" - Id = $engineApiGuid - IsEnabled = $true - Type = "User" - UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." - UserConsentDisplayName = "Access IPAM Engine API" - Value = "access_as_user" + @{ + ResourceAppId = "00000003-0000-0000-c000-000000000000"; + ResourceAccess = @( + @{ + Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; + Type = "Scope" + }, + @{ + Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; + Type = "Scope" + }, + @{ + Id = "37f7f235-527c-4136-accd-4a02d197296e"; + Type = "Scope" + }, + @{ + Id = "14dad69e-099b-42c9-810b-d002981feec1"; + Type = "Scope" + } + ) + }, + @{ + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"; + ResourceAccess = @( + @{ + Id = "41094075-9dad-400e-a0bd-54e686782033"; + Type = "Scope" + } + ) } ) - RequestedAccessTokenVersion = 2 -} + + $engineApiGuid = New-Guid + + $engineApiSettings = @{ + KnownClientApplication = @( + $uiApp.AppId + ) + Oauth2PermissionScope = @( + @{ + AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." + AdminConsentDisplayName = "Access IPAM Engine API" + Id = $engineApiGuid + IsEnabled = $true + Type = "User" + UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." + UserConsentDisplayName = "Access IPAM Engine API" + Value = "access_as_user" + } + ) + RequestedAccessTokenVersion = 2 + } # Create IPAM Engine Application Write-Host "INFO: Creating Azure IPAM Engine Application" -ForegroundColor green @@ -233,10 +222,10 @@ $engineApiSettings = @{ Write-Host "INFO: Updating Azure IPAM UI Application Resource Access" -ForegroundColor green Write-Verbose -Message "Updating Azure IPAM UI Application Resource Access" Update-AzADApplication -ApplicationId $uiApp.AppId -RequiredResourceAccess $uiResourceAccess - + $uiObject = Get-AzADApplication -ApplicationId $uiApp.AppId $engineObject = Get-AzADApplication -ApplicationId $engineApp.AppId - + # Create IPAM UI Service Principal Write-Host "INFO: Creating Azure IPAM UI Service Principal" -ForegroundColor green Write-Verbose -Message "Creating Azure IPAM UI Service Principal" @@ -281,7 +270,7 @@ Function Grant-AdminConsent { scopes = "openid User.Read Directory.Read.All" } ) - + $engineGraphScopes = [System.Collections.ArrayList]@( @{ scopeId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" @@ -346,7 +335,7 @@ Function Grant-AdminConsent { foreach($scope in $engineGraphScopes) { $msGraphId = Get-AzADServicePrincipal ` -ApplicationId $scope.scopeId - + New-MgOauth2PermissionGrant ` -ResourceId $msGraphId.Id ` -Scope $scope.scopes ` From 02ae99b8c338a27fcc9e4d7ec877075b5b225156 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sat, 28 May 2022 10:14:30 -0700 Subject: [PATCH 4/9] Minor update to parameters template --- deploy/main.parameters.example.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/main.parameters.example.json b/deploy/main.parameters.example.json index 7d0b882..75ba28a 100644 --- a/deploy/main.parameters.example.json +++ b/deploy/main.parameters.example.json @@ -3,7 +3,7 @@ "contentVersion": "1.0.0.0", "parameters": { "namePrefix": { - "value": "ipam" + "value": "" }, "tags": { "value": { From da75503a30ab7efc00c6726aecca20c24f6c2045 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sat, 28 May 2022 17:55:59 -0700 Subject: [PATCH 5/9] Added resiliency to graph query and optimized app registration permissions --- deploy/deploy.ps1 | 43 ++++++++++---------------------- ui/src/features/drawer/drawer.js | 35 +++++++++++++++++--------- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index 3f675f3..6eadcdf 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -109,15 +109,23 @@ Function Deploy-IPAMApplications { ResourceAppId = "00000003-0000-0000-c000-000000000000"; ResourceAccess = @( @{ - Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; + Id = "37f7f235-527c-4136-accd-4a02d197296e"; # openid Type = "Scope" }, @{ - Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; + Id = "14dad69e-099b-42c9-810b-d002981feec1"; # profile Type = "Scope" }, @{ - Id = "37f7f235-527c-4136-accd-4a02d197296e"; + Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; # offline_access + Type = "Scope" + }, + @{ + Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; # User.Read + Type = "Scope" + }, + @{ + Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; # Directory.Read.All Type = "Scope" } ) @@ -140,32 +148,11 @@ Function Deploy-IPAMApplications { -Web $uiWebSettings $engineResourceAccess = [System.Collections.ArrayList]@( - @{ - ResourceAppId = "00000003-0000-0000-c000-000000000000"; - ResourceAccess = @( - @{ - Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; - Type = "Scope" - }, - @{ - Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; - Type = "Scope" - }, - @{ - Id = "37f7f235-527c-4136-accd-4a02d197296e"; - Type = "Scope" - }, - @{ - Id = "14dad69e-099b-42c9-810b-d002981feec1"; - Type = "Scope" - } - ) - }, @{ ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"; ResourceAccess = @( @{ - Id = "41094075-9dad-400e-a0bd-54e686782033"; + Id = "41094075-9dad-400e-a0bd-54e686782033"; # user_impersonation Type = "Scope" } ) @@ -267,7 +254,7 @@ Function Grant-AdminConsent { $uiGraphScopes = [System.Collections.ArrayList]@( @{ scopeId = "00000003-0000-0000-c000-000000000000" - scopes = "openid User.Read Directory.Read.All" + scopes = " openid profile offline_access User.Read Directory.Read.All" } ) @@ -276,10 +263,6 @@ Function Grant-AdminConsent { scopeId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" scopes = "user_impersonation" } - @{ - scopeId = "00000003-0000-0000-c000-000000000000" - scopes = "offline_access openid profile User.Read" - } ) # Get Microsoft Graph Access Token diff --git a/ui/src/features/drawer/drawer.js b/ui/src/features/drawer/drawer.js index ca70fe3..e07f396 100644 --- a/ui/src/features/drawer/drawer.js +++ b/ui/src/features/drawer/drawer.js @@ -130,6 +130,7 @@ export default function NavDrawer() { const [menuAnchorEl, setMenuAnchorEl] = React.useState(null); const [mobileMenuAnchorEl, setMobileMenuAnchorEl] = React.useState(null); const [graphData, setGraphData] = React.useState(null); + const [graphError, setGraphError] = React.useState(false); const [navChildOpen, setNavChildOpen] = React.useState({}); const [drawerState, setDrawerState] = React.useState(false); const [settingsOpen, setSettingsOpen] = React.useState(false); @@ -221,19 +222,29 @@ export default function NavDrawer() { ]; React.useEffect(() => { - const request = { - // ...loginRequest, - scopes: ["User.Read"], - account: accounts[0], - forceRefresh: true, - }; + let graphTimer = setTimeout(() => { + const request = { + // ...loginRequest, + scopes: ["User.Read"], + account: accounts[0], + forceRefresh: true, + }; + + (async() => { + try { + const response = await instance.acquireTokenSilent(request); + const graphResponse = await callMsGraph(response.accessToken); + await setGraphData(graphResponse); + } catch { + setGraphError(x => !x); + } + })(); + }, 5000); - (async() => { - const response = await instance.acquireTokenSilent(request); - const graphResponse = await callMsGraph(response.accessToken); - await setGraphData(graphResponse); - })(); - }, []); + return () => { + clearTimeout(graphTimer); + }; + }, [graphError]); // React.useEffect(() => { // const request = { From 33785428f4d6e5745100eedc31a781891cfd0ceb Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sat, 28 May 2022 17:57:30 -0700 Subject: [PATCH 6/9] Update comments on api permissions --- deploy/deploy.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index 6eadcdf..6efd314 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -106,7 +106,7 @@ Function Deploy-IPAMApplications { $uiResourceAccess = [System.Collections.ArrayList]@( @{ - ResourceAppId = "00000003-0000-0000-c000-000000000000"; + ResourceAppId = "00000003-0000-0000-c000-000000000000"; # Microsoft Graph ResourceAccess = @( @{ Id = "37f7f235-527c-4136-accd-4a02d197296e"; # openid @@ -149,7 +149,7 @@ Function Deploy-IPAMApplications { $engineResourceAccess = [System.Collections.ArrayList]@( @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"; + ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"; # Azure Service Management ResourceAccess = @( @{ Id = "41094075-9dad-400e-a0bd-54e686782033"; # user_impersonation From eb16643f4cb58dac509b42cce9494931f47b579a Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sun, 29 May 2022 00:28:46 -0700 Subject: [PATCH 7/9] Few updated comments and removed unneeded scripts --- deploy/deploy.ps1 | 8 +- deploy/deploy_apps.ps1 | 292 ---------------------------------------- deploy/deploy_bicep.ps1 | 74 ---------- 3 files changed, 4 insertions(+), 370 deletions(-) delete mode 100644 deploy/deploy_apps.ps1 delete mode 100644 deploy/deploy_bicep.ps1 diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index 6efd314..d531f56 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -253,14 +253,14 @@ Function Grant-AdminConsent { $uiGraphScopes = [System.Collections.ArrayList]@( @{ - scopeId = "00000003-0000-0000-c000-000000000000" + scopeId = "00000003-0000-0000-c000-000000000000" # Microsoft Graph scopes = " openid profile offline_access User.Read Directory.Read.All" } ) $engineGraphScopes = [System.Collections.ArrayList]@( @{ - scopeId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" + scopeId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management scopes = "user_impersonation" } ) @@ -347,7 +347,7 @@ Function Save-Parameters { # Retrieve JSON object from sample parameter file $parametersObject = Get-Content main.parameters.example.json | ConvertFrom-Json - # update parameter values + # Update Parameter Values $parametersObject.parameters.uiAppId.value = $UIAppId $parametersObject.parameters.engineAppId.value = $EngineAppId $parametersObject.parameters.engineAppSecret.value = $EngineSecret @@ -372,7 +372,7 @@ Function Import-Parameters { # Retrieve JSON object from sample parameter file $parametersObject = Get-Content $ParameterFile | ConvertFrom-Json - # update parameter values + # Read Values from Parameters $UIAppId = $parametersObject.parameters.uiAppId.value $EngineAppId = $parametersObject.parameters.engineAppId.value $EngineSecret = $parametersObject.parameters.engineAppSecret.value diff --git a/deploy/deploy_apps.ps1 b/deploy/deploy_apps.ps1 deleted file mode 100644 index c205f9b..0000000 --- a/deploy/deploy_apps.ps1 +++ /dev/null @@ -1,292 +0,0 @@ -############################################################################################################### -## -## Azure IPAM Application & Service Principal Deployment Script -## -############################################################################################################### - -# Set minimum version requirements -#Requires -Version 7.2 -#Requires -Modules @{ ModuleName="Az"; ModuleVersion="7.5.0"} -#Requires -Modules @{ ModuleName="Microsoft.Graph"; ModuleVersion="1.9.6"} - -# Set global parameters -$engineApiGuid = New-Guid -$logFile = "./deploy_$(get-date -format `"yyyyMMddhhmmsstt`").log" -$tenantId = (Get-AzContext).Tenant.Id - -# Set preference variables -$ErrorActionPreference = "Stop" - -Function deployEngineApplication { - $azureSvcMgmtApiPermissionsScope = "user_impersonation" - $azureSvcMgmtAppId ="797f4846-ba00-4fd7-ba43-dac1f8f63013" - $msGraphApiPermissionsScope = "offline_access openid profile User.Read" - $msGraphAppId = "00000003-0000-0000-c000-000000000000" - $engineResourceAccess = [System.Collections.ArrayList]@( - @{ - ResourceAppId = "00000003-0000-0000-c000-000000000000"; - ResourceAccess = @( - @{ - Id = "7427e0e9-2fba-42fe-b0c0-848c9e6a8182"; - Type = "Scope" - }, - @{ - Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; - Type = "Scope" - }, - @{ - Id = "37f7f235-527c-4136-accd-4a02d197296e"; - Type = "Scope" - }, - @{ - Id = "14dad69e-099b-42c9-810b-d002981feec1"; - Type = "Scope" - } - ) - }, - @{ - ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"; - ResourceAccess = @( - @{ - Id = "41094075-9dad-400e-a0bd-54e686782033"; - Type = "Scope" - } - ) - } - ) - - # Create IPAM engine application - Write-Host "INFO: Creating Azure IPAM Engine Service Principal" -ForegroundColor green - Write-Verbose -Message "Creating Azure IPAM Engine Service Principal" - - $global:engineApp = New-AzADApplication ` - -DisplayName "ipam-engine-app" ` - -RequiredResourceAccess $engineResourceAccess - - # Update IPAM engine application with API endpoint - Update-AzADApplication -ApplicationId $global:engineApp.AppId -IdentifierUri "api://$($global:engineApp.AppId)" - - # Create IPAM engine service principal - $engineSpn = New-AzADServicePrincipal ` - -ApplicationObject $global:engineApp ` - -Role "Reader" ` - -Scope "/providers/Microsoft.Management/managementGroups/$($tenantId)" - - # Create IPAM engine service principal credential - $global:engineSecret = New-AzADAppCredential -ApplicationObject $global:engineApp -StartDate (Get-Date) -EndDate (Get-Date).AddYears(2) - - Write-Host "INFO: Azure IPAM Engine Application & Service Principal created successfully" -ForegroundColor green - Write-Verbose -Message "Azure IPAM Engine Application & Service Principal created successfully" - - # Instantiate Microsoft Graph service principal object - $msGraphSpn = Get-AzADServicePrincipal ` - -ApplicationId $msGraphAppId - - # Instantiate Azure Service Management service principal object - $azureSvcMgmtSpn = Get-AzADServicePrincipal ` - -ApplicationId $azureSvcMgmtAppId - - # Grant admin consent for Microsoft Graph API permissions assigned to IPAM engine application - Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM Engine Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Microsoft Graph API permissions assigned to IPAM Engine Application" - - New-MgOauth2PermissionGrant ` - -ResourceId $msGraphSpn.Id ` - -Scope $msGraphApiPermissionsScope ` - -ClientId $engineSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null - - Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor green - Write-Verbose -Message "Admin consent for Microsoft Graph API permissions granted successfully" - - # Grant admin consent for Azure Service Management API permissions assigned to IPAM application - Write-Host "INFO: Granting admin consent for Azure Service Management API permissions assigned to IPAM ENgine Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Azure Service Management API permissions assigned to IPAM Engine Application" - - New-MgOauth2PermissionGrant ` - -ResourceId $azureSvcMgmtSpn.Id ` - -Scope $azureSvcMgmtApiPermissionsScope ` - -ClientId $engineSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null - - Write-Host "INFO: Admin consent for Azure Service Management API permissions granted successfully" -ForegroundColor green - Write-Verbose -Message "Admin consent for Azure Service Management API API permissions granted successfully" - -} - -Function deployUiApplication { - $engineApiPermissionsScope = "access_as_user" - $msGraphAppId = "00000003-0000-0000-c000-000000000000" - $msGraphApiPermissionsScope = "Directory.Read.All openid User.Read" - $uiResourceAccess = [System.Collections.ArrayList]@( - @{ - ResourceAppId = "00000003-0000-0000-c000-000000000000"; - ResourceAccess = @( - @{ - Id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"; - Type = "Scope" - }, - @{ - Id = "37f7f235-527c-4136-accd-4a02d197296e"; - Type = "Scope" - }, - @{ - Id = "06da0dbc-49e2-44d2-8312-53f166ab848a"; - Type = "Scope" - } - ) - }, - @{ - ResourceAppId = $global:engineApp.AppId; - ResourceAccess = @( - @{ - Id = $engineApiGuid; - Type = "Scope" - } - ) - } - ) - $uiWebSettings = @{ - ImplicitGrantSetting = @{ - EnableAccessTokenIssuance = $true - EnableIdTokenIssuance = $true - } - } - - # Create IPAM UI Application - Write-Host "INFO: Creating Azure IPAM UI Application" -ForegroundColor green - Write-Verbose -Message "Creating Azure IPAM UI Application" - - $global:uiApp = New-AzADApplication ` - -DisplayName "ipam-ui-app" ` - -Web $uiWebSettings ` - -RequiredResourceAccess $uiResourceAccess ` - -SPARedirectUri "https://ipam-placeholder-replace-me.azurewebsites.net" - - # Create IPAM UI service principal - $uiSpn = New-AzADServicePrincipal -ApplicationObject $global:uiApp - - Write-Host "INFO: Azure IPAM UI Application & Service Principal created successfully" -ForegroundColor green - Write-Verbose -Message "Azure IPAM UI Application & Service Principal created successfully" - - # Instantiate Microsoft Graph service principal object - $msGraphSpn = Get-AzADServicePrincipal ` - -ApplicationId $msGraphAppId - - # Instantiate Azure IPAM engine service principal object - $engineSpn = Get-AzADServicePrincipal ` - -ApplicationId $global:engineApp.AppId - - # Grant admin consent for Microsoft Graph API permissions assigned to IPAM UI application - Write-Host "INFO: Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Microsoft Graph API permissions assigned to IPAM UI Application" - - New-MgOauth2PermissionGrant ` - -ResourceId $msGraphSpn.Id ` - -Scope $msGraphApiPermissionsScope ` - -ClientId $uiSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null - - Write-Host "INFO: Admin consent for Microsoft Graph API permissions granted successfully" -ForegroundColor green - Write-Verbose -Message "Admin consent for Microsoft Graph API permissions granted successfully" - - # Grant admin consent for Azure Service Management API permissions assigned to IPAM application - Write-Host "INFO: Granting admin consent for Azure IPAM Engine API permissions assigned to IPAM UI Application" -ForegroundColor Green - Write-Verbose -Message "Granting admin consent for Azure IPAM Engine API permissions assigned to IPAM UI Application" - - New-MgOauth2PermissionGrant ` - -ResourceId $engineSpn.Id ` - -Scope $engineApiPermissionsScope ` - -ClientId $uiSpn.Id ` - -ConsentType AllPrincipals ` - | Out-Null - - Write-Host "INFO: Admin consent for Azure IPAM Engine API permissions granted successfully" -ForegroundColor green - Write-Verbose -Message "Admin consent for Azure IPAM Engine API API permissions granted successfully" - -} - -Function updateEngineApplication { - $engineApiSettings = @{ - KnownClientApplication = @( - $global:uiApp.AppId - ) - Oauth2PermissionScope = @( - @{ - AdminConsentDescription = "Allows the IPAM UI to access IPAM Engine API as the signed-in user." - AdminConsentDisplayName = "Access IPAM Engine API" - Id = $engineApiGuid - IsEnabled = $true - Type = "User" - UserConsentDescription = "Allow the IPAM UI to access IPAM Engine API on your behalf." - UserConsentDisplayName = "Access IPAM Engine API" - Value = "access_as_user" - } - ) - RequestedAccessTokenVersion = 2 - } - - # Update IPAM engine application API settings - Write-Host "INFO: Updating Azure IPAM Engine Application" -ForegroundColor green - Write-Verbose -Message "Updating Azure IPAM Engine Application" - - Update-AzADApplication -ApplicationId $global:engineApp.AppId -Api $engineApiSettings - - Write-Host "INFO: Updated Azure IPAM Engine Application successfully" -ForegroundColor green - Write-Verbose -Message "Updated Azure IPAM Engine Application successfully" - -} - -Function populateBicepParameters { - Write-Host "INFO: Populating Bicep parameter file for infrastructure deployment" -ForegroundColor Green - Write-Verbose -Message "Populating Bicep parameter file for infrastructure deployment" - - # Retrieve JSON object from sample parameter file - $parametersObject = Get-Content main.parameters.example.json | ConvertFrom-Json - - # update parameter values - $parametersObject.parameters.engineAppId.value = $global:engineApp.AppId - - $parametersObject.parameters.engineAppSecret.value = $global:engineSecret.SecretText - - $parametersObject.parameters.uiAppId.value = $global:uiApp.AppId - - # Output updated parameter file for Bicep deployment - $parametersObject | ConvertTo-Json -Depth 3 | Out-File -FilePath main.parameters.json - - Write-Host "INFO: Bicep parameter file populated successfully" -ForegroundColor green - Write-Verbose -Message "Bicep parameter file populated successfully" -} - -try { - # Connect to Microsoft Graph - $accesstoken = (Get-AzAccessToken -Resource "https://graph.microsoft.com/").Token - - Connect-MgGraph -AccessToken $accesstoken - - deployEngineApplication - - deployUiApplication - - updateEngineApplication - - populateBicepParameters - - Write-Host "INFO: IPAM Engine Application Deployment Complete" -ForegroundColor Green - Write-Host "INFO: IPAM Engine Application Display Name: $($global:engineApp.DisplayName)" -ForegroundColor Green - Write-Host "INFO: IPAM Engine Application ID: $($global:engineApp.AppId)" -ForegroundColor Green - Write-Host "INFO: IPAM Engine Application Secret: $($global:engineSecret.SecretText)" -ForegroundColor Green - Write-Host "INFO: IPAM UI Application Deployment Complete" -ForegroundColor Green - Write-Host "INFO: IPAM UI Application Display Name: $($global:uiApp.DisplayName)" -ForegroundColor Green - Write-Host "INFO: IPAM UI Application ID: $($global:uiApp.AppId)" -ForegroundColor Green - -} -catch { - $_ | Out-File -FilePath $logFile -Append - Write-Host "ERROR: Unable to create Azure IPAM Applications due to an exception, see $logFile for detailed information!" -ForegroundColor red - exit - -} diff --git a/deploy/deploy_bicep.ps1 b/deploy/deploy_bicep.ps1 deleted file mode 100644 index ece6bdc..0000000 --- a/deploy/deploy_bicep.ps1 +++ /dev/null @@ -1,74 +0,0 @@ -############################################################################################################### -## -## Azure IPAM Infrastructure Deployment Script -## -############################################################################################################### - -# Set minimum version requirements -#Requires -Version 7.2 -#Requires -Modules @{ ModuleName="Az"; ModuleVersion="7.5.0"} - -# Intake and set global parameters -Param( - [Parameter(Mandatory=$false)] - [string] - $location="westus3" -) -$logFile = "./deploy_$(Get-Date -Format `"yyyyMMddhhmmsstt`").log" - -# Set preference variables -$ErrorActionPreference = "Stop" - -Function validateLocation { - $validLocations = Get-AzLocation - - Write-Host "INFO: Validating Azure Region selected for deployment" -ForegroundColor green - Write-Verbose -Message "Validating Azure Region selected for deployment" - - # Validate Azure Region - if ($location -in ($validLocations | Select-Object -ExpandProperty Location)) { - foreach ($l in $validLocations) { - if ($location -eq $l.Location) { - $script:locationName = $l.DisplayName - - Write-Host "INFO: Azure Region validated successfully" -ForegroundColor green - Write-Verbose -Message "Azure Region validated successfully" - } - } - } - else { - Write-Host "ERROR: Location provided is not a valid Azure Region!" -ForegroundColor red - exit - - } -} - -Function deployBicep { - Write-Host "INFO: Deploying IPAM bicep templates" -ForegroundColor green - Write-Verbose -Message "Deploying bicep templates" - - # Deploy Bicep templates - $global:deployment = New-AzSubscriptionDeployment ` - -Name "ipamInfraDeploy-$(Get-Date -Format `"yyyyMMddhhmmsstt`")" ` - -Location $location ` - -TemplateFile main.bicep ` - -TemplateParameterFile main.parameters.json - - Write-Host "INFO: IPAM bicep templates deployed successfully" -ForegroundColor green - Write-Verbose -Message "IPAM bicep templates deployed successfully" -} - -try { - validateLocation $location - - deployBicep - - Write-Host "WARNING: Remember to update Single-Page Application Redirect URI for UI Application with value: https://$($global:deployment.Outputs["appServiceHostName"].Value)" -ForegroundColor yellow - Write-Verbose -Message "Remember to update Single-Page Application Redirect URI for UI Application with value: https://$($global:deployment.Outputs["appServiceHostName"].Value)" -} -catch { - $_ | Out-File -FilePath $logFile -Append - Write-Host "ERROR: Unable to deploy IPAM Infrastructure due to an exception, see $logFile for detailed information!" -ForegroundColor red - exit - -} From ebe0bcf1477b92e74146f54abaa51133feeebdbf Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sun, 29 May 2022 00:31:05 -0700 Subject: [PATCH 8/9] Updated env example file --- .env.example | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 77d257e..47504bc 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ -CLIENT_ID= -CLIENT_SECRET= +ENGINE_APP_ID= +ENGINE_APP_SECRET= +UI_APP_ID= TENANT_ID= COSMOS_URL=https://.documents.azure.com COSMOS_KEY= From 3be14eb971a870718942c605c7043a0a9a93a239 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sun, 29 May 2022 20:35:19 -0700 Subject: [PATCH 9/9] Added ability to skip admin consent for app registrations and also attach the engine app at the subscription scope --- deploy/deploy.ps1 | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/deploy/deploy.ps1 b/deploy/deploy.ps1 index d531f56..631c05e 100644 --- a/deploy/deploy.ps1 +++ b/deploy/deploy.ps1 @@ -57,6 +57,20 @@ param( [switch] $AppsOnly, + [Parameter(Mandatory = $false, + ParameterSetName = 'Full')] + [Parameter(Mandatory = $false, + ParameterSetName = 'AppsOnly')] + [switch] + $NoConsent, + + [Parameter(Mandatory = $false, + ParameterSetName = 'Full')] + [Parameter(Mandatory = $false, + ParameterSetName = 'AppsOnly')] + [switch] + $SubscriptionScope, + [Parameter(Mandatory = $true, ParameterSetName = 'TemplateOnly')] [ValidateScript({ @@ -101,7 +115,9 @@ Function Deploy-IPAMApplications { [Parameter(Mandatory=$true)] [string]$UIAppName, [Parameter(Mandatory=$true)] - [string]$TenantId + [string]$TenantId, + [Parameter(Mandatory=$false)] + [bool]$SubscriptionScope ) $uiResourceAccess = [System.Collections.ArrayList]@( @@ -218,12 +234,19 @@ Function Deploy-IPAMApplications { Write-Verbose -Message "Creating Azure IPAM UI Service Principal" New-AzADServicePrincipal -ApplicationObject $uiObject | Out-Null + if ($SubscriptionScope) { + $subscriptionId = $(Get-AzContext).Subscription.Id + $scope = "/subscriptions/$subscriptionId" + } else { + $scope = "/providers/Microsoft.Management/managementGroups/$TenantId" + } + # Create IPAM Engine Service Principal Write-Host "INFO: Creating Azure IPAM Engine Service Principal" -ForegroundColor green Write-Verbose -Message "Creating Azure IPAM Engine Service Principal" New-AzADServicePrincipal -ApplicationObject $engineObject ` -Role "Reader" ` - -Scope "/providers/Microsoft.Management/managementGroups/$TenantId" ` + -Scope $scope ` | Out-Null # Create IPAM Engine Secret @@ -480,11 +503,14 @@ try { $appDetails = Deploy-IPAMApplications ` -UIAppName $UIAppName ` -EngineAppName $EngineAppName ` - -TenantId $tenantId + -TenantId $tenantId ` + -SubscriptionScope $SubscriptionScope - Grant-AdminConsent ` - -UIAppId $appDetails.UIAppId ` - -EngineAppId $appDetails.EngineAppId + if (-not $NoConsent) { + Grant-AdminConsent ` + -UIAppId $appDetails.UIAppId ` + -EngineAppId $appDetails.EngineAppId + } } if ($PSCmdlet.ParameterSetName -eq 'AppsOnly') {