Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement e2e private networking where possible #35

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"Organization": "<YourOrganization>",
"Project": "<YourProject>",
"ServiceConnectionName": "<YourServiceConnectionName>",
"Organization": "alsehr",
"Project": "Module Playground",
"ServiceConnectionName": "ModulePlaygroundPrivate",
"AgentPoolProperties": {
"ScaleSetPoolName": "<YourAgentPoolName>",
"ScaleSetPoolName": "custom-pool",
"RecycleAfterEachUse": false,
"MaxSavedNodeCount": 0,
"MaxCapacity": 10,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module scaleSetDeployment '../templates/scaleset.deploy.bicep' = {
params: {
location: location
deploymentsToPerform: deploymentsToPerform
virtualMachineScaleSetComputeGalleryName: '<YourComputeGalleryName>'
virtualMachineScaleSetComputeGalleryName: 'aibgallery'

// Linux example
virtualMachineScaleSetOsType: 'Linux'
Expand Down
8 changes: 4 additions & 4 deletions constructs/azureImageBuilder/deploymentFiles/sbx.image.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module imageDeployment '../templates/image.deploy.bicep' = {
params: {
location: location
deploymentsToPerform: deploymentsToPerform
computeGalleryName: '<computeGalleryName>'
computeGalleryName: 'aibgallery'
imageTemplateComputeGalleryImageDefinitionName: 'sid-linux'
computeGalleryImageDefinitions: [
{
Expand All @@ -37,7 +37,7 @@ module imageDeployment '../templates/image.deploy.bicep' = {
}
]

assetsStorageAccountName: '<assetsStorageAccountName>'
assetsStorageAccountName: 'stalsehraib'
assetsStorageAccountContainerName: 'aibscripts'

storageAccountFilesToUpload: {
Expand Down Expand Up @@ -75,12 +75,12 @@ module imageDeployment '../templates/image.deploy.bicep' = {
{
type: 'Shell'
name: 'PowerShell installation'
scriptUri: 'https://<assetsStorageAccountName>.blob.${az.environment().suffixes.storage}/aibscripts/Install-LinuxPowerShell.sh'
scriptUri: 'https://stalsehraib.blob.${az.environment().suffixes.storage}/aibscripts/Install-LinuxPowerShell.sh'
}
{
type: 'File'
name: 'Initialize-LinuxSoftware'
sourceUri: 'https://<assetsStorageAccountName>.blob.${az.environment().suffixes.storage}/aibscripts/Initialize-LinuxSoftware.ps1'
sourceUri: 'https://stalsehraib.blob.${az.environment().suffixes.storage}/aibscripts/Initialize-LinuxSoftware.ps1'
destination: 'Initialize-LinuxSoftware.ps1'
}
{
Expand Down
118 changes: 78 additions & 40 deletions constructs/azureImageBuilder/templates/image.deploy.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ param deploymentScriptStorageAccountName string = '${assetsStorageAccountName}ds
@description('Optional. The name of container in the Storage Account.')
param assetsStorageAccountContainerName string = 'aibscripts'

// Network Security Group Parameters
// @description('Optional. The name of the Network Security Group to create and attach to the Image Template Subnet.')
// param networkSecurityGroupName string = 'nsg-aib'

// Virtual Network Parameters
@description('Optional. The name of the Virtual Network.')
param virtualNetworkName string = 'vnet-it'
Expand Down Expand Up @@ -95,6 +99,12 @@ param baseTime string = utcNow()

var formattedTime = replace(replace(replace(baseTime, ':', ''), '-', ''), ' ', '')

// Roles
resource storageFileDataPrivilegedContributor 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: '69566ab7-960f-475b-8e7c-b3118f30c6bd' // Storage File Data Priveleged Contributor
scope: tenant()
}

// =========== //
// Deployments //
// =========== //
Expand Down Expand Up @@ -147,11 +157,7 @@ module imageMSI '../../../CARML0.11/managed-identity/user-assigned-identity/main
module imageMSI_rbac '../../../CARML0.11/authorization//role-assignment/subscription/main.bicep' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only infrastructure') {
name: '${deployment().name}-ra'
params: {
// TODO: Tracked issue: https://github.com/Azure/bicep/issues/2371
//principalId: imageMSI.outputs.principalId // Results in: Deployment template validation failed: 'The template resource 'Microsoft.Resources/deployments/image.deploy-ra' reference to 'Microsoft.Resources/deployments/image.deploy-msi' requires an API version. Please see https://aka.ms/arm-template for usage details.'.
// Default: reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('rgParam').name), 'Microsoft.Resources/deployments', format('{0}-msi', deployment().name))).outputs.principalId.value
//principalId: reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, resourceGroupName), 'Microsoft.Resources/deployments', format('{0}-msi', deployment().name)),'2021-04-01').outputs.principalId.value
principalId: (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only infrastructure') ? imageMSI.outputs.principalId : ''
principalId: (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only infrastructure') ? imageMSI.outputs.principalId : '' // Requires condition als Bicep will otherwise try to resolve the null reference
roleDefinitionIdOrName: 'Contributor'
location: location
}
Expand All @@ -171,6 +177,20 @@ module azureComputeGallery '../../../CARML0.11/compute/gallery/main.bicep' = if
]
}

// Network Security Group
// module nsg '../../../CARML0.11/network/network-security-group/main.bicep' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only infrastructure') {
// name: '${deployment().name}-nsg'
// scope: resourceGroup(resourceGroupName)
// params: {
// name: networkSecurityGroupName
// location: location
// }
// dependsOn: [
// rg
// ]
// }
// TODO: https://learn.microsoft.com/en-us/azure/virtual-machines/linux/image-builder-vnet#add-an-nsg-rule

// Image Template Virtual Network
module vnet '../../../CARML0.11/network/virtual-network/main.bicep' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only infrastructure') {
name: '${deployment().name}-vnet'
Expand All @@ -184,6 +204,7 @@ module vnet '../../../CARML0.11/network/virtual-network/main.bicep' = if (deploy
{
name: imageSubnetName
addressPrefix: virtualNetworkSubnetAddressPrefix
// networkSecurityGroupId: (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only infrastructure') ? nsg.outputs.resourceId : '' // Requires condition als Bicep will otherwise try to resolve the null reference
// TODO: Remove once https://github.com/Azure/bicep/issues/6540 is resolved and Private Endpoints are enabled
privateLinkServiceNetworkPolicies: 'Disabled' // Required if using Azure Image Builder with existing VNET
serviceEndpoints: [
Expand Down Expand Up @@ -218,6 +239,11 @@ module vnet '../../../CARML0.11/network/virtual-network/main.bicep' = if (deploy
}

// Assets Storage Account
// Notes
// - Subnet in Stefan's IT deployment missing?
// -> If not provided on my end, doesn't make a difference. Still has access issues
// Storage Firewall
// - Access works if disabled, but is actually enabled in Stefan's deployment
module assetsStorageAccount '../../../CARML0.11/storage/storage-account/main.bicep' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only infrastructure') {
name: '${deployment().name}-files-sa'
scope: resourceGroup(resourceGroupName)
Expand Down Expand Up @@ -251,23 +277,31 @@ module assetsStorageAccount '../../../CARML0.11/storage/storage-account/main.bic
}
]
}
// allowBlobPublicAccess: true // -> Doesn't make a difference.
// publicNetworkAccess: 'Enabled' // -> Doesn't make a difference. Defaults to `null` with networkAcls enabled
// If enabled, the IT cannot access the storage account container files. Also cannot be undone. Once enabled the storage account must be removed and recreated to reset.
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Deny'
virtualNetworkRules: [
{
// Allow image template to access storage account container files to download files
action: 'Allow'
id: vnet.outputs.subnetResourceIds[0]
}
{
// Allow deployment script to access storage account container files to upload files
action: 'Allow'
id: vnet.outputs.subnetResourceIds[1]
}
]
}
}
dependsOn: [
rg
]
}

////////////////////
// TEMP RESOURCES //
////////////////////

// Deployment scripts & their storage account
// Role required for deployment script to be able to use a storage account via private networking
resource storageFileDataPrivilegedContributor 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: '69566ab7-960f-475b-8e7c-b3118f30c6bd' // Storage File Data Priveleged Contributor
scope: tenant()
}

module dsStorageAccount '../../../CARML0.11/storage/storage-account/main.bicep' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only infrastructure') {
name: '${deployment().name}-ds-sa'
scope: resourceGroup(resourceGroupName)
Expand Down Expand Up @@ -304,6 +338,10 @@ module dsStorageAccount '../../../CARML0.11/storage/storage-account/main.bicep'
]
}

////////////////////
// TEMP RESOURCES //
////////////////////

// Upload storage account files
module storageAccount_upload '../../../CARML0.11/resources/deployment-script/main.bicep' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only infrastructure' || deploymentsToPerform == 'Only storage & image') {
name: '${deployment().name}-storage-upload-ds'
Expand Down Expand Up @@ -367,30 +405,30 @@ module imageTemplate '../../../CARML0.11/virtual-machine-images/image-template/m
}

// Deployment script to trigger image build
module imageTemplate_trigger '../../../CARML0.11/resources/deployment-script/main.bicep' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only storage & image' || deploymentsToPerform == 'Only image') {
name: '${deployment().name}-imageTemplate-trigger-ds'
scope: resourceGroup(resourceGroupName)
params: {
name: '${imageTemplateDeploymentScriptName}-${formattedTime}-${(deploymentsToPerform == 'All' || deploymentsToPerform == 'Only storage & image' || deploymentsToPerform == 'Only image') ? imageTemplate.outputs.name : ''}' // Requires condition als Bicep will otherwise try to resolve the null reference
userAssignedIdentities: {
'${az.resourceId(subscription().subscriptionId, resourceGroupName, 'Microsoft.ManagedIdentity/userAssignedIdentities', deploymentScriptManagedIdentityName)}': {}
}
scriptContent: (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only storage & image' || deploymentsToPerform == 'Only image') ? imageTemplate.outputs.runThisCommand : '' // Requires condition als Bicep will otherwise try to resolve the null reference
timeout: 'PT30M'
cleanupPreference: 'Always'
location: location
storageAccountName: deploymentScriptStorageAccountName
subnetIds: [
az.resourceId(subscription().subscriptionId, resourceGroupName, 'Microsoft.Network/virtualNetworks/subnets', virtualNetworkName, deploymentScriptSubnet)
]
}
dependsOn: [
rg
dsMsi
vnet
dsStorageAccount
]
}
// module imageTemplate_trigger '../../../CARML0.11/resources/deployment-script/main.bicep' = if (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only storage & image' || deploymentsToPerform == 'Only image') {
// name: '${deployment().name}-imageTemplate-trigger-ds'
// scope: resourceGroup(resourceGroupName)
// params: {
// name: '${imageTemplateDeploymentScriptName}-${formattedTime}-${(deploymentsToPerform == 'All' || deploymentsToPerform == 'Only storage & image' || deploymentsToPerform == 'Only image') ? imageTemplate.outputs.name : ''}' // Requires condition als Bicep will otherwise try to resolve the null reference
// userAssignedIdentities: {
// '${az.resourceId(subscription().subscriptionId, resourceGroupName, 'Microsoft.ManagedIdentity/userAssignedIdentities', deploymentScriptManagedIdentityName)}': {}
// }
// scriptContent: (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only storage & image' || deploymentsToPerform == 'Only image') ? imageTemplate.outputs.runThisCommand : '' // Requires condition als Bicep will otherwise try to resolve the null reference
// timeout: 'PT30M'
// cleanupPreference: 'Always'
// location: location
// storageAccountName: deploymentScriptStorageAccountName
// subnetIds: [
// az.resourceId(subscription().subscriptionId, resourceGroupName, 'Microsoft.Network/virtualNetworks/subnets', virtualNetworkName, deploymentScriptSubnet)
// ]
// }
// dependsOn: [
// rg
// dsMsi
// vnet
// dsStorageAccount
// ]
// }

@description('The generated name of the image template.')
output imageTemplateName string = (deploymentsToPerform == 'All' || deploymentsToPerform == 'Only storage & image' || deploymentsToPerform == 'Only image') ? imageTemplate.outputs.name : '' // Requires condition als Bicep will otherwise try to resolve the null reference
6 changes: 3 additions & 3 deletions docs/wiki/Creating images with the Azure Image Builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ This section gives you an overview of the solution's structure, that is, how its

<p>

<img src="./media/image/structure.png" alt="Structure" height="400">
<img src="./media/image/structure.png" alt="Structure" height="350">

<p>

Expand Down Expand Up @@ -117,13 +117,13 @@ imageTemplateCustomizationSteps: [
{
type: 'Shell'
name: 'PowerShell installation'
scriptUri: 'https://<assetsStorageAccountName>.blob.core.windows.net/aibscripts/Install-LinuxPowerShell.sh'
scriptUri: 'https://<assetsStorageAccountName>.blob.${az.environment().suffixes.storage}/aibscripts/Install-LinuxPowerShell.sh'
}
{
type: 'Shell'
name: 'Prepare software installation'
inline: [
'wget \'https://<assetsStorageAccountName>.blob.core.windows.net/aibscripts/Initialize-LinuxSoftware.ps1\' -O \'Initialize-LinuxSoftware.ps1\''
'wget \'https://<assetsStorageAccountName>.blob.${az.environment().suffixes.storage}/aibscripts/Initialize-LinuxSoftware.ps1\' -O \'Initialize-LinuxSoftware.ps1\''
'sed -i \'s/\r$//' 'Initialize-LinuxSoftware.ps1\''
'pwsh \'Initialize-LinuxSoftware.ps1\''
]
Expand Down
49 changes: 49 additions & 0 deletions sharedScripts/utilities/Remove-StaleImageTemplate.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<#
.SYNOPSIS
Remove image templates that are stuck in limbo because their User-Assigned Managed Identity was deleted before the image template was deleted.

.DESCRIPTION
Remove image templates that are stuck in limbo because their User-Assigned Managed Identity was deleted before the image template was deleted.
Requires an alternative User-Assigned Identity to temporarily be assigned to the image template, so that it can be deleted.

.PARAMETER UserAssignedIdentityName
Optional. The name of the User-Assigned Identity that will be temporarily assigned to the image template. Defaults to the solution's default value.

.PARAMETER ImageTemplateResourceGroupName
Optional. The resource group to search for image templates in. Defaults to the solution's default value.

.EXAMPLE
Remove-StaleImageTemplate

Remove any image template in the solutions default resource group

.NOTES
The Azure CLI commands in this script will log error messages of the image template during execution (e.g., idenitity required / not authorized). This is expected and can be ignored.
#>
function Remove-StaleImageTemplate {

[CmdletBinding()]
param (
[Parameter(Mandatory = $false)]
[string] $UserAssignedIdentityName = 'msi-aib',

[Parameter(Mandatory = $false)]
[string] $ImageTemplateResourceGroupName = 'rg-ado-agents'
)

if ($userAssignedIdentity = Get-AzResource -ResourceGroupName $ImageTemplateResourceGroupName -ResourceName $UserAssignedIdentityName -ResourceType 'Microsoft.ManagedIdentity/userAssignedIdentities') {
$userAssignedIdentityResourceId = $userAssignedIdentity.ResourceId
} else {
throw "User-Assigned Identity [$UserAssignedIdentityName] not found in resource group [$ImageTemplateResourceGroupName]. Make sure you create it before running this script."
}

$templates = $(az image builder list --query "[?resourceGroup == '$ImageTemplateResourceGroupName'].name" --output tsv)

$templates | ForEach-Object -ThrottleLimit 5 -Parallel {
Write-Output "Processing $_"
az image builder identity remove --resource-group $using:ImageTemplateResourceGroupName -n $_ --user-assigned -y
az image builder delete -g $using:ImageTemplateResourceGroupName -n $_ 2>nul
az image builder identity assign -g $using:ImageTemplateResourceGroupName -n $_ --user-assigned $using:userAssignedIdentityResourceId
az image builder delete -g $using:ImageTemplateResourceGroupName -n $_ --only-show-errors
}
}