Updated deploy script, bicep templates, switches Cosmos DB to use RBAC, switch KeyVault to use RBAC, updated NPM packages, added scripts for version management and building zip asset file and updated docs and screenshots to align with other updates

This commit is contained in:
Matthew Garrett 2024-01-15 22:36:13 -08:00
Родитель 2a49304f17
Коммит ef26ce6bd2
25 изменённых файлов: 675 добавлений и 352 удалений

Двоичные данные
build/ipam.zip → assets/ipam.zip

Двоичный файл не отображается.

Просмотреть файл

@ -12,23 +12,32 @@ param(
[Parameter(ValueFromPipelineByPropertyName = $true,
Mandatory = $true)]
[ValidateScript({
if(Test-Path -LiteralPath $_ -PathType Container) {
return $true
}
elseif(Test-Path -LiteralPath $_ -PathType Leaf) {
throw 'The Path parameter must be a folder, file paths are not allowed.'
}
throw 'Invalid File Path'
if (Test-Path -LiteralPath $_ -PathType Container) {
return $true
}
elseif (Test-Path -LiteralPath $_ -PathType Leaf) {
throw 'The Path parameter must be a folder, file paths are not allowed.'
}
throw 'Invalid File Path'
})]
[string]
$Path,
[Parameter(ValueFromPipelineByPropertyName = $true,
Mandatory = $false)]
[ValidateScript({
if ($_.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -eq -1) {
return $true
}
throw 'File name contains invalid characters'
})]
[string]
$FileName = 'ipam.zip'
)
# Root Directory
$ROOT_DIR = (Get-Item $($MyInvocation.MyCommand.Path)).Directory.Parent.FullName
# Define minimum NodeJS and NPM versions required to build the Azure IPAM UI solution
$MIN_NODE_VERSION = [System.Version]'18.0.0'
$MIN_NPM_VERSION = [System.Version]'8.6.0'
@ -37,92 +46,98 @@ $MIN_NPM_VERSION = [System.Version]'8.6.0'
$ErrorActionPreference = "Stop"
# Set Log File Location
$logPath = [Io.Path]::Combine('..', 'logs')
New-Item -ItemType Directory -Force -Path $logpath | Out-Null
$logPath = Join-Path -Path $ROOT_DIR -ChildPath "logs"
New-Item -ItemType Directory -Path $logpath -Force | Out-Null
$buildLog = Join-Path -Path $logPath -ChildPath "deploy_$(get-date -format `"yyyyMMddhhmmsstt`").log"
$buildLog = Join-Path -Path $logPath -ChildPath "build_$(get-date -format `"yyyyMMddhhmmsstt`").log"
Start-Transcript -Path $buildLog | Out-Null
# Verify NodeJS & NPM are Installed and Meet the Minimum Version Requirements
Write-Host "INFO: Private flag set, verifying NodeJS is present and has the correct version" -ForegroundColor Green
Write-Verbose -Message "Private flag set, verifying NodeJS is present and has the correct version"
try {
Write-Host
Write-Host "INFO: Verifying NodeJS is present and has the correct version" -ForegroundColor Green
# Check for NodeJS and NPM and fetch their current versions
$npmErr = $(
$npmDetails = npm version --json
) 2>&1
# Check for NodeJS and NPM and fetch their current versions
$npmErr = $(
$npmDetails = npm version --json
) 2>&1
# Extract NodeJs and NPM versions and exit if either is not detected
if($null -eq $npmErr) {
$npmDetailsJson = [string]$npmDetails | ConvertFrom-Json
# Extract NodeJs and NPM versions and exit if either is not detected
if($null -eq $npmErr) {
$npmDetailsJson = [string]$npmDetails | ConvertFrom-Json
$npmVersion = [version]$npmDetailsJson.npm
$nodeVersion = [version]$npmDetailsJson.node
} else {
Write-Host "ERROR: NodeJS not detected!" -ForegroundColor red
Write-Host "ERROR: NodeJS is required to build the Azure IPAM code when automatic updates are disabled!" -ForegroundColor red
exit
$npmVersion = [version]$npmDetailsJson.npm
$nodeVersion = [version]$npmDetailsJson.node
} else {
Write-Host "ERROR: NodeJS not detected!" -ForegroundColor red
Write-Host "ERROR: NodeJS is required to build the Azure IPAM code package!" -ForegroundColor red
exit
}
# Check for required NodeJS version
if($nodeVersion -lt $MIN_NODE_VERSION) {
Write-Host "ERROR: NodeJS must be version $MIN_NODE_VERSION or greater!" -ForegroundColor red
}
# Check for required NPM version
if($npmVersion -lt $MIN_NPM_VERSION) {
Write-Host "ERROR: NPM must be version $MIN_NPM_VERSION or greater!" -ForegroundColor red
}
# Exit if NodeJS or NPM versions do not meet the minimum version requirements
if(($nodeVersion -lt $MIN_NODE_VERSION) -or ($npmVersion -lt $MIN_NPM_VERSION)) {
exit
}
Write-Host "INFO: Building application creating ZIP Deploy package" -ForegroundColor Green
# Create path to UI dir from script file location
$uiDir = Join-Path -Path $ROOT_DIR -ChildPath "ui"
# Switch to UI dir for build process
Push-Location -Path $uiDir
Write-Host "INFO: Running NPM Build..." -ForegroundColor Green
# Build Azure IPAM UI
$npmBuildErr = $(
$npmBuild = npm run build --no-update-notifier
) 2>&1
# Switch back to original dir
Pop-Location
# Create the Azure IPAM ZIP Deploy archive if NPM Build was successful
if(-not $npmBuildErr) {
Write-Host "INFO: Creating ZIP Deploy archive..." -ForegroundColor Green
$FilePath = Join-Path -Path $Path -ChildPath $FileName
Compress-Archive -Path ..\engine\app -DestinationPath $FilePath -Force
Compress-Archive -Path ..\engine\requirements.txt -DestinationPath $FilePath -Update
Compress-Archive -Path ..\engine\scripts\* -DestinationPath $FilePath -Update
Compress-Archive -Path ..\engine\ipam-func -DestinationPath $FilePath -Update
Compress-Archive -Path ..\engine\ipam-sentinel -DestinationPath $FilePath -Update
Compress-Archive -Path ..\engine\host.json -DestinationPath $FilePath -Update
Compress-Archive -Path ..\ui\dist -DestinationPath $FilePath -Update
} else {
Write-Host "ERROR: Cannot create ZIP Deploy archive!" -ForegroundColor red
throw $npmBuildErr
}
Write-Host "INFO: Azure IPAM Zip Deploy archive successfully created" -ForegroundColor Green
$fullPath = (Resolve-Path -Path $FilePath).Path
Write-Host
Write-Host "ZIP Asset Path: $fullPath" -ForegroundColor Yellow
}
# Check for required NodeJS version
if($nodeVersion -lt $MIN_NODE_VERSION) {
Write-Host "ERROR: NodeJS must be version $MIN_NODE_VERSION or greater!" -ForegroundColor red
catch {
$_ | Out-File -FilePath $buildLog -Append
Write-Host "ERROR: Unable to build Azure IPAM Zip assets due to an exception, see log for detailed information!" -ForegroundColor red
Write-Host "Build Log: $buildLog" -ForegroundColor Red
}
# Check for required NPM version
if($npmVersion -lt $MIN_NPM_VERSION) {
Write-Host "ERROR: NPM must be version $MIN_NPM_VERSION or greater!" -ForegroundColor red
finally {
Write-Host
Stop-Transcript | Out-Null
}
# Exit if NodeJS or NPM versions do not meet the minimum version requirements
if(($nodeVersion -lt $MIN_NODE_VERSION) -or ($npmVersion -lt $MIN_NPM_VERSION)) {
exit
}
Write-Host "INFO: Building application code and pushing via ZIP Deploy" -ForegroundColor Green
Write-Verbose -Message "Building application code and pushing via ZIP Deploy"
Write-Host "INFO: Running NPM Build..." -ForegroundColor Green
Write-Verbose -Message "INFO: Running NPM Build..."
# Create path to UI dir from script file location
$scriptPath = $MyInvocation.MyCommand.Path
$parentDir = (Get-Item $scriptPath).Directory.Parent.FullName
$uiDir = Join-Path $parentDir "ui"
# Switch to UI dir for build process
Push-Location -Path $uiDir
# Build Azure IPAM UI
$npmBuildErr = $(
$npmBuild = npm run build --no-update-notifier
) 2>&1
# Switch back to original dir
Pop-Location
# Create the Azure IPAM ZIP Deploy archive if NPM Build was successful
if(-not $npmBuildErr) {
Write-Host "INFO: Creating ZIP Deploy archive..." -ForegroundColor Green
Write-Verbose -Message "INFO: Creating ZIP Deploy archive..."
$FilePath = Join-Path -Path $Path -ChildPath $FileName
Compress-Archive -Path ..\engine\app -DestinationPath $FilePath -Force
Compress-Archive -Path ..\engine\requirements.txt -DestinationPath $FilePath -Update
Compress-Archive -Path ..\engine\scripts\* -DestinationPath $FilePath -Update
Compress-Archive -Path ..\engine\ipam-func -DestinationPath $FilePath -Update
Compress-Archive -Path ..\engine\ipam-sentinel -DestinationPath $FilePath -Update
Compress-Archive -Path ..\engine\host.json -DestinationPath $FilePath -Update
Compress-Archive -Path ..\ui\dist -DestinationPath $FilePath -Update
} else {
Write-Host "ERROR: Cannot create ZIP Deploy archive!" -ForegroundColor red
throw $npmBuildErr
}
Write-Host "INFO: Azure IPAM Zip Deploy archive successfully created" -ForegroundColor Green
Write-Verbose -Message "Azure IPAM Zip Deploy archive successfully created"
Write-Host ""
Stop-Transcript | Out-Null

70
build/update.ps1 Normal file
Просмотреть файл

@ -0,0 +1,70 @@
###############################################################################################################
##
## Azure IPAM ZIP Deploy Updater Script
##
###############################################################################################################
# Set minimum version requirements
#Requires -Version 7.2
# Intake and set global parameters
param(
[Parameter(ValueFromPipelineByPropertyName = $true,
Mandatory = $true)]
[string]
$AppName,
[Parameter(ValueFromPipelineByPropertyName = $true,
Mandatory = $true)]
[string]
$ResourceGroupName
)
# Root Directory
$ROOT_DIR = (Get-Item $($MyInvocation.MyCommand.Path)).Directory.Parent.FullName
# Set preference variables
$ErrorActionPreference = "Stop"
# Set Log File Location
$logPath = Join-Path -Path $ROOT_DIR -ChildPath "logs"
New-Item -ItemType Directory -Path $logpath -Force | Out-Null
$updateLog = Join-Path -Path $logPath -ChildPath "update_$(get-date -format `"yyyyMMddhhmmsstt`").log"
Start-Transcript -Path $updateLog | Out-Null
try {
Write-Host
Write-Host "INFO: Uploading ZIP Deploy archive..." -ForegroundColor Green
$zipPath = Join-Path -Path $ROOT_DIR -ChildPath 'assets' -AdditionalChildPath "ipam.zip"
$publishRetries = 5
$publishSuccess = $False
do {
try {
Publish-AzWebApp -ResourceGroupName $ResourceGroupName -Name $AppName -ArchivePath $zipPath -Restart -Force | Out-Null
$publishSuccess = $True
Write-Host "INFO: ZIP Deploy archive successfully uploaded" -ForegroundColor Green
} catch {
if($publishRetries -gt 0) {
Write-Host "WARNING: Problem while uploading ZIP Deploy archive! Retrying..." -ForegroundColor DarkYellow
$publishRetries--
} else {
Write-Host "ERROR: Unable to upload ZIP Deploy archive!" -ForegroundColor Red
throw $_
}
}
} while ($publishSuccess -eq $False -and $publishRetries -gt 0)
}
catch {
$_ | Out-File -FilePath $updateLog -Append
Write-Host "ERROR: Unable to push Azure IPAM ZIP Deploy update due to an exception, see log for detailed information!" -ForegroundColor red
Write-Host "Update Log: $updateLog" -ForegroundColor Red
}
finally {
Write-Host
Stop-Transcript | Out-Null
}

156
build/version.ps1 Normal file
Просмотреть файл

@ -0,0 +1,156 @@
###############################################################################################################
##
## Azure IPAM Version Update Script
##
###############################################################################################################
# Set minimum version requirements
#Requires -Version 7.2
# Intake and set global parameters
[CmdletBinding(DefaultParameterSetName = 'Explicit')]
param(
[Parameter(ValueFromPipelineByPropertyName = $true,
Mandatory = $true,
ParameterSetName = 'Explicit')]
[System.Version]
$Version,
[Parameter(ValueFromPipelineByPropertyName = $true,
Mandatory = $false,
ParameterSetName = 'Implicit')]
[switch]
$BumpMajor,
[Parameter(ValueFromPipelineByPropertyName = $true,
Mandatory = $false,
ParameterSetName = 'Implicit')]
[switch]
$BumpMinor,
[Parameter(ValueFromPipelineByPropertyName = $true,
Mandatory = $false,
ParameterSetName = 'Implicit')]
[switch]
$BumpBuild
)
# Root Directory
$ROOT_DIR = (Get-Item $($MyInvocation.MyCommand.Path)).Directory.Parent.FullName
# Set preference variables
$ErrorActionPreference = "Stop"
# Set Log File Location
$logPath = Join-Path -Path $ROOT_DIR -ChildPath "logs"
New-Item -ItemType Directory -Path $logpath -Force | Out-Null
$versionLog = Join-Path -Path $logPath -ChildPath "version_$(get-date -format `"yyyyMMddhhmmsstt`").log"
Start-Transcript -Path $versionLog | Out-Null
try {
Write-Host
Write-Host "NOTE: Version Update Type: $($PSCmdlet.ParameterSetName)" -ForegroundColor Magenta
# Create path to adjacent directories from script file location
$scriptPath = $MyInvocation.MyCommand.Path
$parentDir = (Get-Item $scriptPath).Directory.Parent.FullName
$uiDir = Join-Path -Path $parentDir -ChildPath "ui"
$engineDir = Join-Path -Path $parentDir -ChildPath "engine" -AdditionalChildPath "app"
$docsDir = Join-Path -Path $parentDir -ChildPath "docs"
Write-Host "INFO: Reading version from UI package.json file" -ForegroundColor Green
# Read version from UI package.json
$packageJsonFile = Join-Path -Path $uiDir -ChildPath "package.json"
$packageJsonContent = Get-Content -Path $packageJsonFile | ConvertFrom-Json
$packageJsonVersion = $packageJsonContent.version
Write-Host "INFO: Reading version from Engine version.json file" -ForegroundColor Green
# Read version from Engine version.json
$engineVersionFile = Join-Path -Path $engineDir -ChildPath "version.json"
$engineVersionContent = Get-Content -Path $engineVersionFile | ConvertFrom-Json
$engineVersion = $engineVersionContent.version
Write-Host "INFO: Reading version from Docs _coverpage.md file" -ForegroundColor Green
# Read version from Docs coverpage.md
$versionPattern = "(?<=<small>).*(?=<\/small>)"
$coverpageFile = Join-Path -Path $docsDir -ChildPath "_coverpage.md"
$coverpageContent = Get-Content -Path $coverpageFile
$coverpageVersion = ($coverpageContent | Select-String $versionPattern).Matches.Groups[0].Value
if ($PSCmdlet.ParameterSetName -eq 'Explicit') {
$updatedVersion = "{0}.{1}.{2}" -f $Version.Major, $Version.Minor, $Version.Build
}
if ($PSCmdlet.ParameterSetName -eq 'Implicit') {
Write-Host "INFO: Calculating new version number" -ForegroundColor Green
$currentVersions = @($packageJsonVersion, $engineVersion, $coverpageVersion)
$notEqual = $currentVersions -ne $currentVersions[0]
if($notEqual) {
Write-Host "ERROR: The current versions do not match" -ForegroundColor Red
Write-Host "ERROR: UI: $packageJsonVersion" -ForegroundColor Red
Write-Host "ERROR: Engine: $engineVersion" -ForegroundColor Red
Write-Host "ERROR: Docs: $coverpageVersion" -ForegroundColor Red
Write-Host "ERROR: Cannot implicitly bump versions" -ForegroundColor Red
throw [System.ArgumentException]::New("Current file versions do not match.")
} else {
$currVer = [System.Version]$currentVersions[0]
}
$majorVersion = $currVer.Major
$minorVersion = $currVer.Minor
$buildVersion = $currVer.Build
if($BumpMajor) {
$majorVersion += 1
}
if($BumpMinor) {
$minorVersion += 1
}
if($BumpBuild) {
$buildVersion += 1
}
$updatedVersion = "{0}.{1}.{2}" -f $majorVersion, $minorVersion, $buildVersion
}
Write-Host "INFO: Updating version for UI package.json file" -ForegroundColor Green
# Update version for UI package.json
$packageJsonContent.version = $updatedVersion
$packageJsonContent | ConvertTo-Json | Set-Content -Path $packageJsonFile
Write-Host "INFO: Updating version for Engine version.json file" -ForegroundColor Green
# Update version for Engine version.json
$engineVersionContent.version = $updatedVersion
$engineVersionContent | ConvertTo-Json | Set-Content -Path $engineVersionFile
Write-Host "INFO: Updating version for Docs coverpage.md file" -ForegroundColor Green
# Update version for Docs coverpage.md
$coverpageContent = $coverpageContent -replace $versionPattern, $updatedVersion
$coverpageContent | Set-Content -Path $coverpageFile
Write-Host "INFO: Azure IPAM versions successfully updated" -ForegroundColor Green
Write-Host
Write-Host "Updated Version -> v$updatedVersion" -ForegroundColor Yellow
}
catch {
$_ | Out-File -FilePath $versionLog -Append
Write-Host "ERROR: Unable to update Azure IPAM component versions due to an exception, see log for detailed information!" -ForegroundColor red
Write-Host "Version Log: $versionLog" -ForegroundColor Red
}
finally {
Write-Host
Stop-Transcript | Out-Null
}

Просмотреть файл

@ -89,10 +89,10 @@ resource appService 'Microsoft.Web/sites@2021-02-01' = {
name: 'COSMOS_URL'
value: cosmosDbUri
}
{
name: 'COSMOS_KEY'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/COSMOS-KEY/)'
}
// {
// name: 'COSMOS_KEY'
// value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/COSMOS-KEY/)'
// }
{
name: 'DATABASE_NAME'
value: databaseName
@ -101,6 +101,10 @@ resource appService 'Microsoft.Web/sites@2021-02-01' = {
name: 'CONTAINER_NAME'
value: containerName
}
{
name: 'MANAGED_IDENTITY_ID'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/IDENTITY-ID/)'
}
{
name: 'UI_APP_ID'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/UI-ID/)'

Просмотреть файл

@ -16,6 +16,13 @@ param location string = resourceGroup().location
@description('Log Analytics Workspace ID')
param workspaceId string
@description('Managed Identity PrincipalId')
param principalId string
var dbContributor = '00000000-0000-0000-0000-000000000002'
var dbContributorId = '${resourceGroup().id}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosAccount.name}/sqlRoleDefinitions/${dbContributor}'
var dbContributorRoleAssignmentId = guid(dbContributor, principalId, cosmosAccount.id)
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2021-04-15' = {
name: cosmosAccountName
location: location
@ -32,6 +39,7 @@ resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2021-04-15' = {
]
databaseAccountOfferType: 'Standard'
enableAutomaticFailover: true
disableKeyBasedMetadataWriteAccess: true
}
}
@ -150,4 +158,14 @@ resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-pr
}
}
resource sqlRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2023-04-15' = {
name: dbContributorRoleAssignmentId
parent: cosmosAccount
properties: {
roleDefinitionId: dbContributorId
principalId: principalId
scope: cosmosAccount.id
}
}
output cosmosDocumentEndpoint string = cosmosAccount.properties.documentEndpoint

Просмотреть файл

@ -218,25 +218,21 @@ DynamicParam {
$validators.Remove('appServiceName')
}
$attrFull = [System.Management.Automation.ParameterAttribute]::new()
$attrFull.ParameterSetName = 'ResourceNames'
$attrFull.ParameterSetName = "App"
$attrFull.Mandatory = $false
$attrApp = [System.Management.Automation.ParameterAttribute]::new()
$attrApp.ParameterSetName = "App"
$attrApp.Mandatory = $false
$attrTemplateOnly = [System.Management.Automation.ParameterAttribute]::new()
$attrTemplateOnly.ParameterSetName = 'ResourceNames'
$attrTemplateOnly.ParameterSetName = "AppContainer"
$attrTemplateOnly.Mandatory = $false
$attrAppContainer = [System.Management.Automation.ParameterAttribute]::new()
$attrAppContainer.ParameterSetName = "AppContainer"
$attrAppContainer.Mandatory = $false
$attrFunction = [System.Management.Automation.ParameterAttribute]::new()
$attrFunction.ParameterSetName = 'ResourceNames'
$attrFunction.ParameterSetName = "Function"
$attrFunction.Mandatory = $false
$attrFunction = [System.Management.Automation.ParameterAttribute]::new()
$attrFunction.ParameterSetName = 'ResourceNames'
$attrFunction.ParameterSetName = "FunctionContainer"
$attrFunction.Mandatory = $false
$attrFunctionContainer = [System.Management.Automation.ParameterAttribute]::new()
$attrFunctionContainer.ParameterSetName = "FunctionContainer"
$attrFunctionContainer.Mandatory = $false
$attrValidation = [System.Management.Automation.ValidateScriptAttribute]::new({
$invalidFields = [System.Collections.ArrayList]@()
@ -276,9 +272,10 @@ DynamicParam {
})
$attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection.Add($attrFull)
$attributeCollection.Add($attrTemplateOnly)
$attributeCollection.Add($attrApp)
$attributeCollection.Add($attrAppContainer)
$attributeCollection.Add($attrFunction)
$attributeCollection.Add($attrFunctionContainer)
$attributeCollection.Add($attrValidation)
$param = [System.Management.Automation.RuntimeDefinedParameter]::new('ResourceNames', [hashtable], $attributeCollection)
@ -298,8 +295,13 @@ process {
AzureChinaCloud = "AZURE_CHINA"
}
# Root Directory
$ROOT_DIR = (Get-Item $($MyInvocation.MyCommand.Path)).Directory.Parent.FullName
# Minimum Required Azure CLI Version
$MIN_AZ_CLI_VER = [System.Version]'2.35.0'
# Check for Debug Flag
$DEBUG_MODE = [bool]$PSCmdlet.MyInvocation.BoundParameters[Debug].IsPresent
# Set preference variables
@ -311,8 +313,8 @@ process {
$Env:SuppressAzurePowerShellBreakingChangeWarnings = $true
# Set Log File Location
$logPath = [Io.Path]::Combine('..', 'logs')
New-Item -ItemType Directory -Force -Path $logpath | Out-Null
$logPath = Join-Path -Path $ROOT_DIR -ChildPath "logs"
New-Item -ItemType Directory -Path $logpath -Force| Out-Null
$debugLog = Join-Path -Path $logPath -ChildPath "debug_$(get-date -format `"yyyyMMddhhmmsstt`").log"
$errorLog = Join-Path -Path $logPath -ChildPath "error_$(get-date -format `"yyyyMMddhhmmsstt`").log"
@ -387,7 +389,7 @@ process {
# Create IPAM UI Application (If -UI:$false not specified)
if (-not $DisableUI) {
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" `
@ -396,20 +398,20 @@ process {
$engineResourceMap = @{
"AZURE_PUBLIC" = @{
ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"
ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033")
ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management
ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation
}
"AZURE_US_GOV" = @{
ResourceAppId = "40a69793-8fe6-4db1-9591-dbc5c57b17d8"
ResourceAccessIds = @("8eb49ffc-05ac-454c-9027-8648349217dd", "e59ee429-1fb1-4054-b99f-f542e8dc9b95")
ResourceAppId = "40a69793-8fe6-4db1-9591-dbc5c57b17d8" # Azure Service Management
ResourceAccessIds = @("8eb49ffc-05ac-454c-9027-8648349217dd", "e59ee429-1fb1-4054-b99f-f542e8dc9b95") # user_impersonation
}
"AZURE_GERMANY" = @{
ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"
ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033")
ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management
ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation
}
"AZURE_CHINA" = @{
ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013"
ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033")
ResourceAppId = "797f4846-ba00-4fd7-ba43-dac1f8f63013" # Azure Service Management
ResourceAccessIds = @("41094075-9dad-400e-a0bd-54e686782033") # user_impersonation
}
}
@ -469,17 +471,17 @@ process {
$engineApiSettings.Add("KnownClientApplication", $knownClientApplication)
}
# 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
$engineApp = New-AzADApplication `
-DisplayName $EngineAppName `
-Api $engineApiSettings `
-RequiredResourceAccess $engineResourceAccessList
# Update IPAM Engine API Endpoint
Write-Host "INFO: Updating Azure IPAM Engine API Endpoint" -ForegroundColor Green
Write-Verbose -Message "Updating Azure IPAM Engine API Endpoint"
# Update IPAM Engine API Endpoint
Update-AzADApplication -ApplicationId $engineApp.AppId -IdentifierUri "api://$($engineApp.AppId)"
$uiEngineApiAccess =@{
@ -497,7 +499,7 @@ process {
# Update IPAM UI Application Resource Access (If -UI:$false not specified)
if (-not $DisableUI) {
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
@ -508,31 +510,29 @@ process {
# Create IPAM UI Service Principal (If -UI:$false not specified)
if (-not $DisableUI) {
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
}
$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"
# Create IPAM Engine Service Principal
New-AzADServicePrincipal -ApplicationObject $engineObject `
-Role "Reader" `
-Scope $scope `
| Out-Null
# Create IPAM Engine Secret
Write-Host "INFO: Creating Azure IPAM Engine Secret" -ForegroundColor Green
Write-Verbose -Message "Creating Azure IPAM Engine Secret"
# 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
Write-Verbose -Message "Azure IPAM Engine & UI Applications/Service Principals created successfully"
} else {
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 = @{
@ -603,9 +603,9 @@ process {
$accesstoken = ConvertTo-SecureString $accesstoken -AsPlainText -Force
}
# Connect to Microsoft Graph
Write-Host "INFO: Logging in to Microsoft Graph" -ForegroundColor Green
Write-Verbose -Message "Logging in to Microsoft Graph"
# Connect to Microsoft Graph
Connect-MgGraph -Environment $msGraphMap[$AzureCloud].Environment -AccessToken $accesstoken | Out-Null
# Fetch Azure IPAM UI Service Principal (If -UI:$false not specified)
@ -621,7 +621,7 @@ process {
# Grant admin consent for Microsoft Graph API permissions assigned to IPAM UI application (If -UI:$false not specified)
if (-not $DisableUI) {
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
@ -635,13 +635,12 @@ process {
}
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 to the IPAM UI application for exposed API from the IPAM Engine application (If -UI:$false not specified)
if (-not $DisableUI) {
Write-Host "INFO: Granting admin consent to the IPAM UI application for exposed API from the IPAM Engine application" -ForegroundColor Green
Write-Verbose -Message "Granting admin consent to the IPAM UI application for exposed API from the IPAM Engine application"
New-MgOauth2PermissionGrant `
-ResourceId $engineSpn.Id `
-Scope "access_as_user" `
@ -650,12 +649,11 @@ process {
| Out-Null
Write-Host "INFO: Admin consent for IPAM Engine exposed API granted successfully" -ForegroundColor Green
Write-Verbose -Message "Admin consent for IPAM Engine exposed API granted successfully"
}
# Grant admin consent for Azure Service Management API permissions assigned to IPAM Engine 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 Engine application
foreach($scope in $engineGraphScopes) {
$msGraphId = Get-AzADServicePrincipal `
-ApplicationId $scope.scopeId
@ -669,7 +667,6 @@ process {
}
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 permissions granted successfully"
}
Function Save-Parameters {
@ -685,7 +682,6 @@ process {
)
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
@ -705,7 +701,6 @@ process {
$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 {
@ -715,7 +710,6 @@ process {
)
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
@ -742,7 +736,6 @@ process {
# $deployType = $script:AsFunction ? 'Function' : 'Full'
Write-Host "INFO: Successfully import Bicep parameter values for deployment" -ForegroundColor Green
Write-Verbose -Message "Successfully import Bicep parameter values for deployment"
$appDetails = @{
UIAppId = $UIAppId
@ -778,7 +771,6 @@ process {
)
Write-Host "INFO: Deploying IPAM bicep templates" -ForegroundColor Green
Write-Verbose -Message "Deploying bicep templates"
# Instantiate deployment parameter object
$deploymentParameters = @{
@ -830,7 +822,6 @@ process {
$DebugPreference = 'SilentlyContinue'
Write-Host "INFO: IPAM bicep templates deployed successfully" -ForegroundColor Green
Write-Verbose -Message "IPAM bicep template deployed successfully"
return $deployment
}
@ -844,7 +835,6 @@ process {
)
Write-Host "INFO: Updating UI Application with SPA configuration" -ForegroundColor Green
Write-Verbose -Message "Updating UI Application with SPA configuration"
$appServiceEndpoint = "https://$Endpoint"
@ -852,7 +842,6 @@ process {
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"
}
# Main Deployment Script Section
@ -865,10 +854,9 @@ process {
try {
if($PrivateAcr) {
# Verify Minimum Azure CLI Version
Write-Host "INFO: PrivateACR flag set, verifying minimum Azure CLI version" -ForegroundColor Green
Write-Verbose -Message "PrivateACR flag set, verifying minimum Azure CLI version"
# Verify Minimum Azure CLI Version
$azureCliVer = [System.Version](az version | ConvertFrom-Json).'azure-cli'
if($azureCliVer -lt $MIN_AZ_CLI_VER) {
@ -876,10 +864,9 @@ process {
exit
}
# Verify Azure PowerShell and Azure CLI Contexts Match
Write-Host "INFO: PrivateACR flag set, verifying Azure PowerShell and Azure CLI contexts match" -ForegroundColor Green
Write-Verbose -Message "PrivateACR flag set, verifying Azure PowerShell and Azure CLI contexts match"
# Verify Azure PowerShell and Azure CLI Contexts Match
$azureCliContext = $(az account show | ConvertFrom-Json) 2>$null
if(-not $azureCliContext) {
@ -897,25 +884,23 @@ process {
}
if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer', '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"
# Fetch Tenant ID
$tenantId = (Get-AzContext).Tenant.Id
# Fetch Azure Cloud Type
Write-Host "INFO: Fetching Azure Cloud type from Azure PowerShell SDK" -ForegroundColor Green
Write-Verbose -Message "Fetching Azure Cloud type from Azure PowerShell SDK"
# Fetch Azure Cloud Type
$azureCloud = $AZURE_ENV_MAP[(Get-AzContext).Environment.Name]
}
if ($PSCmdlet.ParameterSetName -in ('App', 'AppContainer', 'Function', 'FunctionContainer')) {
# Validate Azure Region
Write-Host "INFO: Validating Azure Region selected for deployment" -ForegroundColor Green
Write-Verbose -Message "Validating Azure Region selected for deployment"
# Validate Azure Region
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
@ -969,9 +954,8 @@ process {
if ($PSCmdlet.ParameterSetName -in ('App', 'Function')) {
Write-Host "INFO: Uploading ZIP Deploy archive..." -ForegroundColor Green
Write-Verbose -Message "INFO: Uploading ZIP Deploy archive..."
$zipPath = Join-Path '../build' -ChildPath "ipam.zip"
$zipPath = Join-Path -Path $ROOT_DIR -ChildPath 'assets' -AdditionalChildPath "ipam.zip"
$publishRetries = 5
$publishSuccess = $False
@ -980,6 +964,7 @@ process {
try {
Publish-AzWebApp -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -Name $deployment.Outputs["appServiceName"].Value -ArchivePath $zipPath -Restart -Force | Out-Null
$publishSuccess = $True
Write-Host "INFO: ZIP Deploy archive successfully uploaded" -ForegroundColor Green
} catch {
if($publishRetries -gt 0) {
Write-Host "WARNING: Problem while uploading ZIP Deploy archive! Retrying..." -ForegroundColor DarkYellow
@ -994,7 +979,6 @@ process {
if ($PSCmdlet.ParameterSetName -in ('AppContainer', 'FunctionContainer') -and $PrivateAcr) {
Write-Host "INFO: Building and pushing container images to Azure Container Registry" -ForegroundColor Green
Write-Verbose -Message "Building and pushing container images to Azure Container Registry"
$containerMap = @{
Debian = @{
@ -1015,39 +999,34 @@ process {
}
}
$dockerPath = [Io.Path]::Combine('..')
$dockerFile = Join-Path -Path $dockerPath -ChildPath 'Dockerfile'
$dockerFileFunc = Join-Path -Path $dockerPath -ChildPath 'Dockerfile.func'
$dockerFile = Join-Path -Path $ROOT_DIR -ChildPath 'Dockerfile'
$dockerFileFunc = Join-Path -Path $ROOT_DIR -ChildPath 'Dockerfile.func'
if($Function) {
Write-Host "INFO: Building Function container..." -ForegroundColor Green
Write-Verbose -Message "INFO: Building Function container..."
$funcBuildOutput = $(
az acr build -r $deployment.Outputs["acrName"].Value `
-t ipamfunc:latest `
-f $dockerFileFunc $dockerPath
-f $dockerFileFunc $ROOT_DIR
) *>&1
if ($LASTEXITCODE -ne 0) {
throw $funcBuildOutput
} else {
Write-Host "INFO: Function container image build and push completed successfully" -ForegroundColor Green
Write-Verbose -Message "Function container image build and push completed successfully"
}
Write-Host "INFO: Restarting Function App" -ForegroundColor Green
Write-Verbose -Message "Restarting Function App"
Restart-AzFunctionApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value -Force | Out-Null
} else {
Write-Host "INFO: Building App container ($ContainerType)..." -ForegroundColor Green
Write-Verbose -Message "INFO: Building App container ($ContainerType)..."
$appBuildOutput = $(
az acr build -r $deployment.Outputs["acrName"].Value `
-t ipam:latest `
-f $dockerFile $dockerPath `
-f $dockerFile $ROOT_DIR `
--build-arg PORT=$($containerMap[$ContainerType].Port) `
--build-arg BUILD_IMAGE=$($containerMap[$ContainerType].Images.Build) `
--build-arg SERVE_IMAGE=$($containerMap[$ContainerType].Images.Serve)
@ -1057,18 +1036,15 @@ process {
throw $appBuildOutput
} else {
Write-Host "INFO: App container image build and push completed successfully" -ForegroundColor Green
Write-Verbose -Message "App container image build and push completed successfully"
}
Write-Host "INFO: Restarting App Service" -ForegroundColor Green
Write-Verbose -Message "Restarting App Service"
Restart-AzWebApp -Name $deployment.Outputs["appServiceName"].Value -ResourceGroupName $deployment.Outputs["resourceGroupName"].Value | Out-Null
}
}
Write-Host "INFO: Azure IPAM Solution deployed successfully" -ForegroundColor Green
Write-Verbose -Message "Azure IPAM Solution deployed successfully"
if ($($PSCmdlet.ParameterSetName -notin 'AppsOnly') -and (-not $DisableUI) -and $ParameterFile) {
$updateUrl = "https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Authentication/appId/$($appDetails.UIAppId)"

Просмотреть файл

@ -92,10 +92,10 @@ resource functionApp 'Microsoft.Web/sites@2021-03-01' = {
name: 'COSMOS_URL'
value: cosmosDbUri
}
{
name: 'COSMOS_KEY'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/COSMOS-KEY/)'
}
// {
// name: 'COSMOS_KEY'
// value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/COSMOS-KEY/)'
// }
{
name: 'DATABASE_NAME'
value: databaseName
@ -104,6 +104,10 @@ resource functionApp 'Microsoft.Web/sites@2021-03-01' = {
name: 'CONTAINER_NAME'
value: containerName
}
{
name: 'MANAGED_IDENTITY_ID'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/IDENTITY-ID/)'
}
{
name: 'UI_APP_ID'
value: '@Microsoft.KeyVault(SecretUri=${keyVaultUri}secrets/UI-ID/)'

Просмотреть файл

@ -5,7 +5,10 @@ param keyVaultName string
param location string = resourceGroup().location
@description('Managed Identity PrincipalId')
param principalId string
param identityPrincipalId string
@description('Managed Identity ClientId')
param identityClientId string
@description('AzureAD TenantId')
param tenantId string = subscription().tenantId
@ -23,26 +26,17 @@ param engineAppSecret string
@description('Log Analytics Worskpace ID')
param workspaceId string
// KeyVault Secret Permissions Assigned to Managed Identity
var secretsPermissions = [
'get'
]
var keyVaultUser = '4633458b-17de-408a-b874-0445c86b69e6'
var keyVaultUserId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', keyVaultUser)
var keyVaultUserRoleAssignmentId = guid(keyVaultUser, identityPrincipalId, keyVault.id)
resource keyVault 'Microsoft.KeyVault/vaults@2021-11-01-preview' = {
name: keyVaultName
location: location
properties: {
enablePurgeProtection: true
enableRbacAuthorization: true
tenantId: tenantId
accessPolicies: [
{
objectId: principalId
tenantId: tenantId
permissions: {
secrets: secretsPermissions
}
}
]
sku: {
name: 'standard'
family: 'A'
@ -54,6 +48,14 @@ resource keyVault 'Microsoft.KeyVault/vaults@2021-11-01-preview' = {
}
}
resource identityId 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
parent: keyVault
name: 'IDENTITY-ID'
properties: {
value: identityClientId
}
}
resource uiId 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
parent: keyVault
name: 'UI-ID'
@ -114,5 +116,15 @@ resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-pr
}
}
resource keyVaultUserAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
name: keyVaultUserRoleAssignmentId
scope: keyVault
properties: {
principalType: 'ServicePrincipal'
roleDefinitionId: keyVaultUserId
principalId: identityPrincipalId
}
}
output keyVaultName string = keyVault.name
output keyVaultUri string = keyVault.properties.vaultUri

Просмотреть файл

@ -87,7 +87,8 @@ module keyVault 'keyVault.bicep' = {
params: {
location: location
keyVaultName: resourceNames.keyVaultName
principalId: managedIdentity.outputs.principalId
identityPrincipalId: managedIdentity.outputs.principalId
identityClientId: managedIdentity.outputs.clientId
uiAppId: uiAppId
engineAppId: engineAppId
engineAppSecret: engineAppSecret
@ -106,6 +107,7 @@ module cosmos 'cosmos.bicep' = {
cosmosDatabaseName: resourceNames.cosmosDatabaseName
keyVaultName: keyVault.outputs.keyVaultName
workspaceId: logAnalyticsWorkspace.outputs.workspaceId
principalId: managedIdentity.outputs.principalId
}
}

Просмотреть файл

@ -1,19 +1,15 @@
@description('Contributor Role Assignment GUID')
param contributorAssignmentName string = newGuid()
@description('Deployment Location')
param location string = resourceGroup().location
@description('Managed Identity Name')
param managedIdentityName string
@description('Managed Identity Operator Role Assignment GUID')
param managedIdentityOperatorAssignmentName string = newGuid()
var contributor = 'b24988ac-6180-42a0-ab88-20f7382dd24c'
var contributorId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', contributor)
var contributorRoleAssignmentId = guid(contributor, managedIdentity.id, subscription().id)
var managedIdentityOperator = 'f1a07417-d97a-45cb-824c-7a7467783830'
var managedIdentityOperatorId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', managedIdentityOperator)
var managedIdentityOperatorRoleAssignmentId = guid(managedIdentityOperator, managedIdentity.id, subscription().id)
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
name: managedIdentityName
@ -21,8 +17,7 @@ resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-
}
resource contributorAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
#disable-next-line use-stable-resource-identifiers
name: contributorAssignmentName
name: contributorRoleAssignmentId
properties: {
principalType: 'ServicePrincipal'
roleDefinitionId: contributorId
@ -31,8 +26,7 @@ resource contributorAssignment 'Microsoft.Authorization/roleAssignments@2020-04-
}
resource managedIdentityOperatorAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
#disable-next-line use-stable-resource-identifiers
name: managedIdentityOperatorAssignmentName
name: managedIdentityOperatorRoleAssignmentId
properties: {
principalType: 'ServicePrincipal'
roleDefinitionId: managedIdentityOperatorId

Просмотреть файл

@ -1,4 +1,4 @@
# Welcome to IPAM!
# Welcome to Azure IPAM!
<!--
Guidelines on README format: https://review.docs.microsoft.com/help/onboard/admin/samples/concepts/readme-template?branch=master
@ -9,51 +9,60 @@ Taxonomies for products and languages: https://review.docs.microsoft.com/new-hop
-->
## Overview and Architecture
IPAM was developed to give customers a simple, straightforward way to manage their IP address space in Azure. IPAM enables end-to-end planning, deploying, managing and monitoring of your IP address space, with an intuitive user experience. IPAM automatically discovers IP address utilization in your Azure tenant and enables you to manage it all from a centralized UI. You can also interface with IPAM programmatically via a RESTful API to facilitate IP address management at scale via Infrastructure as Code (IaC). IPAM is designed and architected based on the 5 pillars of the [Microsoft Azure Well Architected Framework](https://docs.microsoft.com/en-us/azure/architecture/framework/).
Azure IPAM was developed to give customers a simple, straightforward way to manage their IP address space in Azure. It enables end-to-end planning, deploying, managing and monitoring of your IP address space, with an intuitive user experience. Additionally, it can automatically discover IP address utilization within your Azure tenant and enables you to manage it all from a centralized UI. You can also interface with the Azure IPAM service programmatically via a RESTful API to facilitate IP address management at scale via Infrastructure as Code (IaC) and CI/CD pipelines. Azure IPAM is designed and architected based on the 5 pillars of the [Microsoft Azure Well Architected Framework](https://docs.microsoft.com/en-us/azure/architecture/framework/).
| Full (App Service) | Function |
| App Service | Function |
:-----------------------------------------------------------------:|:---------------------------------------------------------------------------:
| ![IPAM Architecture](./images/ipam_architecture_full.png ':size=70%') | ![IPAM Architecture](./images/ipam_architecture_function.png ':size=70%') |
## IPAM Infrastructure
The IPAM solution is comprised of containers running on Azure App Services. IPAM can also be deployed in an API-only fashion with an Azure Function if no UI is required (e.g. pure IaC model). The containers are built and published to a public Azure Container Registry (ACR), but you may also choose to build your own containers and host them in a Private Container Registry. More details on this can be found in the [Deployment](./deployment/README.md) section. All of the supporting infrastructure is deployed and runs within your Azure Tenant, none of the resources are shared with other IPAM users (outside of the publicly hosted ACR).
## Azure IPAM Infrastructure
The Azure IPAM solution is delivered via a container running in Azure App Services or as an Azure Function. It can also be deployed in an API-only fashion if no UI is required (e.g. pure IaC model). The container is built and published to a public Azure Container Registry (ACR), but you may also choose to build your own container and host it in a Private Container Registry. More details on this can be found in the [Deployment](./deployment/README.md) section. All of the supporting infrastructure is deployed and runs within your Azure Tenant and none of the resources are shared with other IPAM users (outside of the publicly hosted ACR).
Here is a more specific breakdown of the components used:
- **App Registrations**
- 2x App Registrations
- *Engine* App Registration
- Granted **reader** permission to the root management group to facilitate IPAM Admin operations (global visibility)
- Granted **reader** permission to the [Root Management Group](https://learn.microsoft.com/en-us/azure/governance/management-groups/overview#root-management-group-for-each-directory) to facilitate IPAM Admin operations (global visibility)
- Authentication point for IPAM API operations ([on-behalf-of](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow) flow)
- *UI* App Registration
- *UI* App Registration *(Optional if no UI is desired)*
- Granted **read** permissions for Microsoft Graph API's
- Added as a *known client application* for the *Engine* App Registration
- Authentication point for the IPAM UI ([auth code](https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow) flow)
- **Resource Group**
- House all Azure infrastructure related resources
- **App Service Plan with App Service** *(Full Deployment only)*
- Run the IPAM Engine, UI, and Load Balancer containers as a multi-container App Service
- **App Service Plan with Function App** *(Function Deployment only)*
- Run IPAM Engine as an Azure Function
- **Storage Account with Blob Container** *(Function Deployment only)*
- This account stores the Function metadata
- Contains all Azure IPAM deployed resources
- **App Service Plan with App Service** *(AppContainer Deployment only)*
- Runs the Azure IPAM solution as a container within App Services
- **App Service Plan with Function App** *(FunctionContainer Deployment only)*
- Runs the Azure IPAM solution as a container within Azure Functions
- **Storage Account with Blob Container** *(FunctionContainer Deployment only)*
- Storage for the Azure Function metadata
- **Cosmos DB**
- Backend NoSQL datastore for the IPAM application
- **KeyVault**
- Stores the following secrets:
- App Registration application IDs and Secrets (Engine & UI)
- Cosmos DB read-write key
- Managed Identity ID
- Azure Tenant ID
- **User Assigned Managed Identity**
- Assigned to the App Service to retrieve secrets from KeyVault and NGINX configuration data from the Storage Account
- Assigned to the App Service to retrieve secrets from KeyVault
## How IPAM Works
## How Azure IPAM Works
As mentioned above, the IPAM application is made up of two containers, one that runs the front end user interface, and the other that runs the backend engine. For the *Full* deployment, there is also a Load Balancer container running [NGINX](https://www.nginx.com/) for path-based routing. IPAM has been designed as such to accommodate the following use cases...
Azure IPAM has been designed as such to radically simplify the often daunting task of IP address management within Azure and was built to accommodate use cases such as the following...
- A user interface is not needed or you plan on providing your own user interface (API-only)
- You plan on interfacing with IPAM exclusively via the RESTful API
- You plan on running the backend engine in a lightweight fashion, such as Azure Functions or Azure Container Instances
- Discovery
- Identify networks, subnets and endpoints holistically across your Azure tenant
- Visualize misconfigurations such as orphaned endpoints and improperly configured virtual network peers
- Organize
- Group Azure networks into *Spaces* and *Blocks* aligned to internal lines of business and enterprise CIDR assignments
- Track IP and CIDR consumption
- Map external (non-Azure) networks to Azure CIDR ranges
- Plan
- Explore "what if" cases such as how may subnets of a given mask are available within a given CIDR block
- Self-Service
- Allow users to reserve CIDR blocks for new virtual network and subnet creation programatically
- Integration with Azure template deployments (ARM/Bicep), Terraform and CI/CD pipelines
## User Interface

Просмотреть файл

@ -1,7 +1,7 @@
## IPAM REST API Overview
You can interface with the full set of capabilities of IPAM via a REST API. We use Swagger to define API documentation in OpenAPI v3 Specification format.
## Azure IPAM REST API Overview
You can interface with the full set of capabilities of Azure IPAM via a REST API. We use Swagger to define API documentation in OpenAPI v3 Specification format.
API docs can be found at the `/api/docs` path of your IPAM website. Here you will find information on methods, parameters, and request body details for all available APIs.
API docs can be found at the `/api/docs` path of your Azure IPAM website. Here you will find information on methods, parameters, and request body details for all available APIs.
![IPAM openapi specification](./images/openapispec.png)
@ -9,7 +9,7 @@ API docs can be found at the `/api/docs` path of your IPAM website. Here you wil
You can interface with the API like you would any other REST API. We'll be using [Postman](https://www.postman.com) and [Azure PowerShell](https://docs.microsoft.com/en-us/powershell/azure/what-is-azure-powershell) for our examples.
## Obtaining an Azure AD Token
First things first, you'll need to obtain an Azure AD token for authentication purposes. You can retrieve one via the IPAM UI at anytime by selecting **Token** from the menu presented when clicking on your user avatar in the upper righthand corner.
First things first, you'll need to obtain an Azure AD token for authentication purposes. You can retrieve one via the Azure IPAM UI at anytime by selecting **Token** from the menu presented when clicking on your user avatar in the upper righthand corner.
![IPAM azure ad token](./images/token.png)
@ -17,7 +17,7 @@ You'll then be presented with a message notifying you that your token has been s
![IPAM azure ad token clipboard](./images/token_clipboard.png)
You can also retrieve an Azure AD token from IPAM via Azure PowerShell by using the [Get-AzAccessToken](https://docs.microsoft.com/en-us/powershell/module/az.accounts/get-azaccesstoken) commandlet. The token is retrieved from the API exposed via the backend engine application registration. This is the **ResourceUrl** you will be making the access token call against via Azure PowerShell.
You can also retrieve an Azure AD token from Azure IPAM via Azure PowerShell by using the [Get-AzAccessToken](https://docs.microsoft.com/en-us/powershell/module/az.accounts/get-azaccesstoken) commandlet. The token is retrieved from the API exposed via the backend engine application registration. This is the **ResourceUrl** you will be making the access token call against via Azure PowerShell.
![IPAM api resource url](./images/ipam_api_resource_url.png)
@ -90,10 +90,10 @@ $response
id : ABNsJjXXyTRDTRCdJEJThu
cidr : 10.1.5.0/24
userId : harvey@elnica6yahoo.onmicrosoft.com
userId : user@ipam.onmicrosoft.com
createdOn : 1662514052.26623
status : wait
tag : @{X-IPAM-RES-ID=ABNsJjXXyTRDTRCdJEJThu}
````
Take a look at our **Azure Landing Zone integration** example found under the `deploy` directory in the repository for a real work example of how to automate vNET creation by means of Bicep and leveraging the IPAM API.
Take a look at our **Azure Landing Zone integration** example found under the `deploy` directory in the repository for a real work example of how to automate vNET creation by means of Bicep and leveraging the Azure IPAM API.

Просмотреть файл

@ -12,7 +12,7 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Running an IPAM Development Environment with Docker Compose
## Running an Azure IPAM Development Environment with Docker Compose
We have included a Docker Compose file in the root directory of the project (`docker-compose.yml`), to quickly build a fully functional Azure IPAM development environment. The Docker Compose file is also dependant on an `env` file to correctly pass all of the required environment variables into the containers. You can use the `env.example` file, also found at the root directory of the project, as a template to create your own `env` file.
To start a development environment of the Azure IPAM solution via Docker Compose, run the following commands from the root directory of the project:
@ -29,32 +29,24 @@ docker compose rm -s -v -f
```
## Building Production Containers Images and Pushing them to DockerHub
We user Dockerfiles to build the containers for each of the Azure IPAM supporting components including the Engine, UI, and Load Balancer. If you choose, you can build these containers yourself and host them in DockerHub.
We use Dockerfiles to build the containers for the Azure IPAM solution and have two located in the root directory of the project. One is designed for use when running inside a solution such as Azure App Services (as well as other containerized environments) and another specifically designed for running inside Azure Functions. If you choose, you can build these containers yourself and host them in DockerHub.
To do so, run the following Docker commands from the root directory of the project:
```shell
# Engine Container
docker build --rm --no-cache -t <Repository Name>/ipam-engine:latest -f ./engine/Dockerfile.deb ./engine
docker push <Repository Name>/ipam-engine:latest
# App Services Container
docker build --rm --no-cache -t <Repository Name>/ipam:latest -f ./Dockerfile .
docker push <Repository Name>/ipam:latest
# Function Container
docker build --rm --no-cache -t <Repository Name>/ipam-func:latest -f ./engine/Dockerfile.func ./engine
docker push <Repository Name>/ipam-func:latest
# UI Container
docker build --rm --no-cache -t <Repository Name>/ipam-ui:latest -f ./ui/Dockerfile.deb ./ui
docker push <Repository Name>/ipam-ui:latest
# Load Balancer Container
docker build --rm --no-cache -t <Repository Name>/ipam-lb:latest -f ./lb/Dockerfile ./lb
docker push <Repository Name>/ipam-lb:latest
docker build --rm --no-cache -t <Repository Name>/ipamfunc:latest -f ./Dockerfile.func .
docker push <Repository Name>/ipamfunc:latest
```
## Building Production Containers Images Using a Private ACR
## Building & Updating Production Containers Images Using a Private ACR
In addition to the DockerHub option (above), alternatively you may choose to leverage an Azure Container Registry to host your Azure IPAM containers. Also, you may have selected the `-PrivateACR` flag during the deployment of your Azure IPAM environment, and from time to time you will need to update your containers as new code is released.
To do so, run the following Azure CLI commands from the root directory of the project:
Before running the update commands, you'll need to authenticate to the Azure CLI
```shell
# Authenicate to Azure CLI
@ -62,7 +54,21 @@ az login
# Set Target Azure Subscription
az account set --subscription "<Target Subscription Name/GUID>"
```
Next, use the following commands to update the Azure IPAM containers within your private Azure Container Registry
```shell
# App Services Container
az acr build -r <ACR Name> -t ipam:latest -f ./Dockerfile .
# Function Container
az acr build -r <ACR Name> -t ipamfunc:latest -f .Dockerfile.func .
```
If you're using the legacy Azure IPAM multi-container deployment (prior to v3.0.0), please use the following commands to update your containers instead
```shell
# Engine Container
az acr build -r <ACR Name> -t ipam-engine:latest -f ./engine/Dockerfile.deb ./engine

Просмотреть файл

@ -1,4 +1,4 @@
# IPAM Deployment Overview
# Azure IPAM Deployment Overview
## Prerequisites
@ -12,31 +12,38 @@ To successfully deploy the solution, the following prerequisites must be met:
- [User Access Administrator](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#user-access-administrator)
- [Custom Role](https://learn.microsoft.com/en-us/azure/role-based-access-control/custom-roles) with *allow* permissions of `Microsoft.Authorization/roleAssignments/write`
- [Global Administrator](https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#global-administrator) (needed to grant admin consent for the App Registration API permissions)
- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) installed
- Required to clone the Azure IPAM GitHub repository
- [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell) version 7.2.0 or later installed
- [Azure PowerShell](https://learn.microsoft.com/en-us/powershell/azure/install-az-ps) version 8.0.0 or later installed
- [Microsoft Graph PowerShell SDK](https://learn.microsoft.com/en-us/powershell/microsoftgraph/installation) version 1.9.6 or later installed
- [Microsoft Graph PowerShell SDK](https://learn.microsoft.com/en-us/powershell/microsoftgraph/installation) version 2.0.0 or later installed
- Required for *Full* or *Apps Only* deployments to grant [Admin Consent](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/grant-admin-consent) to the App Registrations
- [Bicep CLI](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install) version 0.10.161 or later installed
- [Bicep CLI](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install) version 0.21.1 or later installed
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) version 2.35.0 or later installed (optional)
- Required only if you are building your own container images and pushing them to Azure Container Registry (Private ACR)
- Docker (Linux) / Docker Desktop (Windows) installed (optional)
- Required only if you are building your own container images and running them locally for development/testing purposes
- Required only if you are building your own container image and pushing it to a private Azure Container Registry (Private ACR)
- [Docker (Linux)](https://docs.docker.com/engine/install/) / [Docker Desktop (Windows)](https://docs.docker.com/desktop/install/windows-install/) installed (optional)
- Required only if you are building your own container image and running it locally for development/testing purposes
## Deployment Overview
The Azure IPAM solution is deployed via a PowerShell deployment script, `deploy.ps1`, found in the `deploy` directory of the project. The infrastructure stack is defined via Azure Bicep files. The deployment can be performed via your local machine or from the development container found in the project. You have the following options for deployment:
- Two-part deployment
- Part 1: App Registrations only
- Two-part deployment *(Azure Identities and Permissions Only)*
- Part 1: Azure Identities only
- App Registrations and Service Principals are created
- Required permissions are assigned to App Registrations and Service Principals
- Configuration details are saved to a `parameters.json` file which will be shared with the infrastructure team
- Part 2: Infrastructure Stack only
- UI and Engine containers hosted in App Service or...
- Engine container hosted in an Azure Function
- Deploy the entire solution (App Registrations + Azure Infrastructure)
- UI and Engine containers hosted in App Service or...
- Engine container hosted in an Azure Function
- Part 2: Azure Infrastructure only
- Parameters are read from supplied `parameters.json` file
- Azure infrastructure components are deployed
- Azure App Service is pointed to public or private Azure Container Registry
- Single deployment *(Azure Identities, Permissions and Infrastructure)*
- App Registrations and Service Principals are created
- Required permissions are assigned to App Registrations and Service Principals
- Azure infrastructure components are deployed
- Azure App Service is pointed to public or private Azure Container Registry
The two-part deployment option is provided in the event that a single team doesn't have the necessary permissions to deploy both the App Registrations in Azure AD, and the Azure infrastructure stack. In the event that you do have all of the the necessary permissions in Azure AD and on the Azure infrastructure side, then you have the option to deploy the entire solution all at once.
The two-part deployment option is provided in the event that a single team within your organization doesn't have the necessary permissions to deploy both the Azure identities within Entra ID, and the Azure infrastructure stack. If a single group does have all of the the necessary permissions in Entra ID and on the Azure infrastructure side, then you have the option to deploy the complete solution all at once.
## Authenticate to Azure PowerShell
@ -122,15 +129,16 @@ To deploy the full solution, run the following from within the `deploy` director
You have the ability to pass optional flags to the deployment script:
| Parameter | Description |
| :---------------------------------------------- | :------------------------------------------------------------------------ |
| `-UIAppName <name>` | Changes the name of the UI app registration |
| `-EngineAppName <name>` | Changes the name of the Engine app registration |
| `-Tags @{<tag> = '<value>'; <tag> = '<value>'}` | Attaches the hashtable as tags on the deployed IPAM resource group |
| `-ResourceNames @{<resource1> = '<name>'; <resource2> = '<name>'}` | Overrides default resource names with custom names **<sup>1,2</sup>** |
| `-NamePrefix <prefix>` | Replaces the default resource prefix of "ipam" with an alternative prefix **<sup>3</sup>** |
| `-AsFunction` | Deploys the engine container only to an Azure Function |
| `-PrivateACR` | Deploys a private Azure Container Registry and builds the IPAM containers |
| Parameter | Description |
| :----------------------------------------------------------------- | :----------------------------------------------------------------------------------------- |
| `-UIAppName <name>` | Changes the name of the UI app registration |
| `-EngineAppName <name>` | Changes the name of the Engine app registration |
| `-Tags @{<tag> = '<value>'; <tag> = '<value>'}` | Attaches the hashtable as tags on the deployed IPAM resource group |
| `-ResourceNames @{<resource1> = '<name>'; <resource2> = '<name>'}` | Overrides default resource names with custom names **<sup>1,2</sup>** |
| `-NamePrefix <prefix>` | Replaces the default resource prefix of "ipam" with an alternative prefix **<sup>3</sup>** |
| `-Function` | Deploys the engine container only to an Azure Function |
| `-PrivateACR` | Deploys a private Azure Container Registry and builds the IPAM containers |
| `-DisableUI` | Solution will be deployed without a UI, no UI identities will be created |
> **NOTE 1:** The required values will vary based on the deployment type.
@ -168,7 +176,7 @@ You have the ability to pass optional flags to the deployment script:
```powershell
./deploy.ps1 `
-Location "westus3" `
-AsFunction
-Function
```
**Deploy IPAM solution with a private Container Registry:**
@ -199,7 +207,7 @@ $ResourceNames = @{
-ResourceNames $ResourceNames
```
**Override default resource names with custom resource names and deploy as an Azure Function:**
**Override default resource names with custom resource names and use a private Container Registry:**
```powershell
$ResourceNames = @{
@ -217,11 +225,11 @@ $ResourceNames = @{
./deploy.ps1 `
-Location "westus3" `
-ResourceNames $ResourceNames
-ResourceNames $ResourceNames `
-PrivateACR
```
**Override default resource names with custom resource names and use a private Container Registry:**
**Override default resource names with custom resource names and deploy as an Azure Function:**
```powershell
$ResourceNames = @{
@ -240,12 +248,12 @@ $ResourceNames = @{
./deploy.ps1 `
-Location "westus3" `
-ResourceNames $ResourceNames
-AsFunction
-Function
```
## App Registration Only Deployment
## Azure Identities (Only) Deployment
To deploy App Registrations only, run the following from within the `deploy` directory:
To deploy Azure Identities only, run the following from within the `deploy` directory:
```powershell
./deploy.ps1 -AppsOnly
@ -253,11 +261,11 @@ To deploy App Registrations only, run the following from within the `deploy` dir
You have the ability to pass optional flags to the deployment script:
| Parameter | Description |
| :---------------------- | :-------------------------------------------------------------------------------------------------- |
| `-UIAppName <name>` | Changes the name of the UI app registration |
| `-EngineAppName <name>` | Changes the name of the Engine app registration |
| `-AsFunction` | Indicates that this solution will be deployed as an Azure Function, no UI App Registration required |
| Parameter | Description |
| :---------------------- | :----------------------------------------------------------------------- |
| `-UIAppName <name>` | Changes the name of the UI app registration |
| `-EngineAppName <name>` | Changes the name of the Engine app registration |
| `-DisableUI` | Solution will be deployed without a UI, no UI identities will be created |
**Customize the name of the App Registrations:**
@ -268,15 +276,15 @@ You have the ability to pass optional flags to the deployment script:
-EngineAppName "my-engine-app-reg"
```
**Deploy IPAM solution as an Azure Function:**
**Deploy IPAM solution without a UI (API-only):**
```powershell
./deploy.ps1 `
-AppsOnly `
-AsFunction
-DisableUI
```
As part of the app registration deployment, a `main.parameters.json` file is generated with pre-populated parameters for the app registration IDs as well as the engine app registration secret. This parameter file will then be used to perform the infrastructure deployment.
As part of the app registration deployment, a `main.parameters.json` file is generated with pre-populated parameters for the app registration IDs as well as the engine app registration secret. This parameter file will then be used to perform the infrastructure only deployment.
## Infrastructure Stack (Only) Deployment
@ -292,12 +300,13 @@ Once your parameters file is ready, run the following from within the `deploy` d
You have the ability to pass optional flags to the deployment script:
| Parameter | Description |
| :---------------------------------------------- | :------------------------------------------------------------------------ |
| `-Tags @{<tag> = '<value>'; <tag> = '<value>'}` | Attaches the hashtable as tags on the deployed IPAM resource group |
| `-ResourceNames @{<resource1> = '<name>'; <resource2> = '<name>'}` | Overrides default resource names with custom names **<sup>1,2</sup>** |
| `-NamePrefix <prefix>` | Replaces the default resource prefix of "ipam" with an alternative prefix **<sup>3</sup>** |
| `-PrivateACR` | Deploys a private Azure Container Registry and builds the IPAM containers |
| Parameter | Description |
| :----------------------------------------------------------------- | :----------------------------------------------------------------------------------------- |
| `-Tags @{<tag> = '<value>'; <tag> = '<value>'}` | Attaches the hashtable as tags on the deployed IPAM resource group |
| `-ResourceNames @{<resource1> = '<name>'; <resource2> = '<name>'}` | Overrides default resource names with custom names **<sup>1,2</sup>** |
| `-NamePrefix <prefix>` | Replaces the default resource prefix of "ipam" with an alternative prefix **<sup>3</sup>** |
| `-PrivateACR` | Deploys a private Azure Container Registry and builds the IPAM containers |
| `-Function` | Deploys the engine container only to an Azure Function |
> **NOTE 1:** The required values will vary based on the deployment type.
@ -305,6 +314,15 @@ You have the ability to pass optional flags to the deployment script:
> **NOTE 3:** Maximum of seven (7) characters. This is because the prefix is used to generate names for several different Azure resource types with varying maximum lengths.
**Change the name prefix for the Azure resources:**
```powershell
./deploy.ps1 `
-Location "westus3" `
-ParameterFile ./main.parameters.json `
-NamePrefix "devipam"
```
**Add custom tags to the Azure resources:**
```powershell
@ -314,13 +332,13 @@ You have the ability to pass optional flags to the deployment script:
-Tags @{owner = 'ipamadmin@example.com'; environment = 'development'}
```
**Change the name prefix for the Azure resources:**
**Deploy IPAM solution as an Azure Function:**
```powershell
./deploy.ps1 `
-Location "westus3" `
-ParameterFile ./main.parameters.json `
-NamePrefix "devipam"
-Function
```
**Deploy IPAM solution with a private Container Registry:**
@ -372,7 +390,7 @@ $ResourceNames = @{
./deploy.ps1 `
-Location "westus3" `
-ParameterFile ./main.parameters.json `
-ResourceNames $ResourceNames
-ResourceNames $ResourceNames `
-PrivateACR
```
@ -395,7 +413,6 @@ $ResourceNames = @{
./deploy.ps1 `
-Location "westus3" `
-ParameterFile ./main.parameters.json `
-ResourceNames $ResourceNames
-ResourceNames $ResourceNames `
-Function
```
> **NOTE:** Use this format when the `-AsFunction` flag was used during the *App Registration Only* step above

Двоичные данные
docs/images/ipam_architecture_full.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 147 KiB

После

Ширина:  |  Высота:  |  Размер: 207 KiB

Двоичные данные
docs/images/ipam_architecture_function.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 117 KiB

После

Ширина:  |  Высота:  |  Размер: 160 KiB

Просмотреть файл

@ -1,11 +1,11 @@
# Questions or Comments
The IPAM team welcomes engagement and contributions from the community.
The Azure IPAM team welcomes engagement and contributions from the community.
## Discussions
We have set up a [GitHub Discussions](https://github.com/Azure/ipam/discussions) page to make it easy to engage with the IPAM team without opening an issue.
We have set up a [GitHub Discussions](https://github.com/Azure/ipam/discussions) page to make it easy to engage with the Azure IPAM team without opening an issue.
## Issues
In the event you come across a bug in the IPAM tool, please open a [GitHub Issue](https://github.com/Azure/ipam/issues) and someone from the team will respond as soon ASAP. Please include as much detail as you can about what actions were taken leading up to the discovery of the issue, and how (if possible) it can be reproduced.
In the event you come across a bug in the Azure IPAM tool, please open a [GitHub Issue](https://github.com/Azure/ipam/issues) and someone from the team will respond as soon ASAP. Please include as much detail as you can about what actions were taken leading up to the discovery of the issue, and how (if possible) it can be reproduced.

Просмотреть файл

@ -131,3 +131,5 @@ az cosmosdb update --resource-group <ResourceGroupName> --name <CosmosDBAccountN
#### <u>Notes</u>
This flag may have been set by [Azure Policy](https://learn.microsoft.com/en-us/azure/governance/policy/overview). You can find more details about this policy [here](https://learn.microsoft.com/en-us/azure/cosmos-db/policy-reference#azure-cosmos-db) under *Azure Cosmos DB key based metadata write access should be disabled*. You may need to contact your policy administrator to request an exception for Azure IPAM.
Additionally this issue only applies to legacy deployments of Azure IPAM (prior to v3.0.0) as the latest versions use SQL [role-based access control](https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac) to read/write data from Cosmos DB.

Просмотреть файл

@ -34,6 +34,10 @@ class Globals:
session = aiohttp.ClientSession(connector=conn)
self.shared_transport = AioHttpTransport(session=session, session_owner=False)
@property
def MANAGED_IDENTITY_ID(self):
return os.environ.get('MANAGED_IDENTITY_ID')
@property
def CLIENT_ID(self):
return os.environ.get('CLIENT_ID') or os.environ.get('ENGINE_APP_ID')

Просмотреть файл

@ -7,9 +7,11 @@ from fastapi.middleware.gzip import GZipMiddleware
from fastapi_restful.tasks import repeat_every
from fastapi.encoders import jsonable_encoder
from azure.identity.aio import ManagedIdentityCredential
from azure.cosmos.aio import CosmosClient
from azure.cosmos import PartitionKey
from azure.cosmos.exceptions import CosmosResourceExistsError, CosmosResourceNotFoundError
from azure.cosmos.exceptions import CosmosResourceExistsError, CosmosResourceNotFoundError, CosmosHttpResponseError
from app.routers import (
azure,
@ -40,7 +42,9 @@ from app.routers.common.helper import (
cosmos_replace
)
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
BUILD_DIR = os.path.join(os.getcwd(), "dist")
IPAM_VERSION = json.load(open(os.path.join(ROOT_DIR, "version.json")))['version']
try:
UI_APP_ID = uuid.UUID(os.environ.get('UI_APP_ID'))
@ -56,7 +60,7 @@ Azure IPAM is a lightweight solution developed on top of the Azure platform desi
app = FastAPI(
title = "Azure IPAM",
description = description,
version = "3.0.0",
version = IPAM_VERSION,
contact = {
"name": "Azure IPAM Team",
"url": "https://github.com/azure/ipam",
@ -168,12 +172,20 @@ if os.path.isdir(BUILD_DIR) and UI_APP_ID and VALID_APP_ID:
return FileResponse(BUILD_DIR + "/index.html")
async def db_upgrade():
cosmos_client = CosmosClient(globals.COSMOS_URL, credential=globals.COSMOS_KEY)
managed_identity_credential = ManagedIdentityCredential(
client_id = globals.MANAGED_IDENTITY_ID
)
database_name = "ipam-db"
cosmos_client = CosmosClient(
globals.COSMOS_URL,
credential=globals.COSMOS_KEY if globals.COSMOS_KEY else managed_identity_credential,
transport=globals.SHARED_TRANSPORT
)
database_name = globals.DATABASE_NAME
database = cosmos_client.get_database_client(database_name)
container_name = "ipam-container"
container_name = globals.CONTAINER_NAME
container = database.get_container_client(container_name)
try:
@ -362,6 +374,7 @@ async def db_upgrade():
# logger.info("No existing Virtual Hubs to patch...")
await cosmos_client.close()
await managed_identity_credential.close()
@app.on_event("startup")
async def ipam_startup():
@ -402,32 +415,46 @@ async def ipam_startup():
with open(env_file, "w") as env_file:
env_file.write(env_data_js)
client = CosmosClient(globals.COSMOS_URL, credential=globals.COSMOS_KEY)
managed_identity_credential = ManagedIdentityCredential(
client_id = globals.MANAGED_IDENTITY_ID
)
cosmos_client = CosmosClient(
globals.COSMOS_URL,
credential=globals.COSMOS_KEY if globals.COSMOS_KEY else managed_identity_credential,
transport=globals.SHARED_TRANSPORT
)
database_name = globals.DATABASE_NAME
try:
logger.info('Creating Database...')
database = await client.create_database(
logger.info('Verifying Database Exists...')
database = await cosmos_client.create_database_if_not_exists(
id = database_name
)
except CosmosResourceExistsError:
logger.warning('Database exists! Using existing database...')
database = client.get_database_client(database_name)
except CosmosHttpResponseError as e:
logger.error('Cosmos database does not exist, error initializing Azure IPAM!')
raise e
database = cosmos_client.get_database_client(database_name)
container_name = globals.CONTAINER_NAME
try:
logger.info('Creating Container...')
container = await database.create_container(
logger.info('Verifying Container Exists...')
container = await database.create_container_if_not_exists(
id = container_name,
partition_key = PartitionKey(path = "/tenant_id")
)
except CosmosResourceExistsError:
logger.warning('Container exists! Using existing container...')
container = database.get_container_client(container_name)
except CosmosHttpResponseError as e:
logger.error('Cosmos container does not exist, error initializing Azure IPAM!')
raise e
container = database.get_container_client(container_name)
await client.close()
await cosmos_client.close()
await managed_identity_credential.close()
await db_upgrade()

Просмотреть файл

@ -1,6 +1,6 @@
from fastapi import HTTPException
from azure.identity.aio import OnBehalfOfCredential, ClientSecretCredential
from azure.identity.aio import OnBehalfOfCredential, ManagedIdentityCredential, ClientSecretCredential
from azure.core import MatchConditions
from azure.core.exceptions import ClientAuthenticationError, HttpResponseError, ServiceRequestError
@ -18,9 +18,13 @@ from functools import wraps
from app.globals import globals
managed_identity_credential = ManagedIdentityCredential(
client_id = globals.MANAGED_IDENTITY_ID
)
cosmos_client = CosmosClient(
url=globals.COSMOS_URL,
credential=globals.COSMOS_KEY,
credential=(globals.COSMOS_KEY if globals.COSMOS_KEY else managed_identity_credential),
transport=globals.SHARED_TRANSPORT
)

3
engine/app/version.json Normal file
Просмотреть файл

@ -0,0 +1,3 @@
{
"version": "3.0.0"
}

64
ui/package-lock.json сгенерированный
Просмотреть файл

@ -30,8 +30,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-redux": "^9.0.4",
"react-router-dom": "^6.21.1",
"react-redux": "^9.1.0",
"react-router-dom": "^6.21.2",
"spinners-react": "^1.0.7",
"web-vitals": "^3.5.1"
},
@ -1642,9 +1642,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz",
"integrity": "sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==",
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz",
"integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==",
"engines": {
"node": ">=14.0.0"
}
@ -5235,9 +5235,9 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/react-redux": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz",
"integrity": "sha512-9J1xh8sWO0vYq2sCxK2My/QO7MzUMRi3rpiILP/+tDr8krBHixC6JMM17fMK88+Oh3e4Ae6/sHIhNBgkUivwFA==",
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz",
"integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==",
"dependencies": {
"@types/use-sync-external-store": "^0.0.3",
"use-sync-external-store": "^1.0.0"
@ -5270,11 +5270,11 @@
}
},
"node_modules/react-router": {
"version": "6.21.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.1.tgz",
"integrity": "sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA==",
"version": "6.21.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.2.tgz",
"integrity": "sha512-jJcgiwDsnaHIeC+IN7atO0XiSRCrOsQAHHbChtJxmgqG2IaYQXSnhqGb5vk2CU/wBQA12Zt+TkbuJjIn65gzbA==",
"dependencies": {
"@remix-run/router": "1.14.1"
"@remix-run/router": "1.14.2"
},
"engines": {
"node": ">=14.0.0"
@ -5284,12 +5284,12 @@
}
},
"node_modules/react-router-dom": {
"version": "6.21.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.1.tgz",
"integrity": "sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA==",
"version": "6.21.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.2.tgz",
"integrity": "sha512-tE13UukgUOh2/sqYr6jPzZTzmzc70aGRP4pAjG2if0IP3aUT+sBtAKUJh0qMh0zylJHGLmzS+XWVaON4UklHeg==",
"dependencies": {
"@remix-run/router": "1.14.1",
"react-router": "6.21.1"
"@remix-run/router": "1.14.2",
"react-router": "6.21.2"
},
"engines": {
"node": ">=14.0.0"
@ -7465,9 +7465,9 @@
}
},
"@remix-run/router": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz",
"integrity": "sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow=="
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz",
"integrity": "sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg=="
},
"@rollup/pluginutils": {
"version": "5.0.5",
@ -10049,9 +10049,9 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"react-redux": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.0.4.tgz",
"integrity": "sha512-9J1xh8sWO0vYq2sCxK2My/QO7MzUMRi3rpiILP/+tDr8krBHixC6JMM17fMK88+Oh3e4Ae6/sHIhNBgkUivwFA==",
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz",
"integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==",
"requires": {
"@types/use-sync-external-store": "^0.0.3",
"use-sync-external-store": "^1.0.0"
@ -10064,20 +10064,20 @@
"dev": true
},
"react-router": {
"version": "6.21.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.1.tgz",
"integrity": "sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA==",
"version": "6.21.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.2.tgz",
"integrity": "sha512-jJcgiwDsnaHIeC+IN7atO0XiSRCrOsQAHHbChtJxmgqG2IaYQXSnhqGb5vk2CU/wBQA12Zt+TkbuJjIn65gzbA==",
"requires": {
"@remix-run/router": "1.14.1"
"@remix-run/router": "1.14.2"
}
},
"react-router-dom": {
"version": "6.21.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.1.tgz",
"integrity": "sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA==",
"version": "6.21.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.2.tgz",
"integrity": "sha512-tE13UukgUOh2/sqYr6jPzZTzmzc70aGRP4pAjG2if0IP3aUT+sBtAKUJh0qMh0zylJHGLmzS+XWVaON4UklHeg==",
"requires": {
"@remix-run/router": "1.14.1",
"react-router": "6.21.1"
"@remix-run/router": "1.14.2",
"react-router": "6.21.2"
}
},
"react-transition-group": {

Просмотреть файл

@ -26,8 +26,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-redux": "^9.0.4",
"react-router-dom": "^6.21.1",
"react-redux": "^9.1.0",
"react-router-dom": "^6.21.2",
"spinners-react": "^1.0.7",
"web-vitals": "^3.5.1"
},