From a1c0ff68cc008defddd95196bee9f36279f46e56 Mon Sep 17 00:00:00 2001 From: Dylan Prins <8777848+Dylan-Prins@users.noreply.github.com> Date: Mon, 17 Jun 2024 09:34:29 +0200 Subject: [PATCH] adding private functionality for webApp --- deploy/main.bicep | 28 ++++++++++++++- deploy/modules/appService.bicep | 45 +++++++++++++++-------- deploy/modules/containerRegistry.bicep | 30 +++++++++++++++- deploy/modules/cosmos.bicep | 25 +++++++++++++ deploy/modules/functionApp.bicep | 49 ++++++++++++++++---------- deploy/modules/keyVault.bicep | 24 +++++++++++++ deploy/modules/storageAccount.bicep | 31 ++++++++++++++-- deploy/modules/virtualNetwork.bicep | 44 +++++++++++++++++++++++ 8 files changed, 238 insertions(+), 38 deletions(-) create mode 100644 deploy/modules/virtualNetwork.bicep diff --git a/deploy/main.bicep b/deploy/main.bicep index fa29bb9..49abdbf 100644 --- a/deploy/main.bicep +++ b/deploy/main.bicep @@ -36,6 +36,8 @@ param engineAppSecret string @description('Tags') param tags object = {} +param private bool = false + @description('IPAM Resource Names') param resourceNames object = { functionName: '${namePrefix}-${uniqueString(guid)}' @@ -51,6 +53,7 @@ param resourceNames object = { resourceGroupName: '${namePrefix}-rg-${uniqueString(guid)}' storageAccountName: '${namePrefix}stg${uniqueString(guid)}' containerRegistryName: '${namePrefix}acr${uniqueString(guid)}' + virtualNetwork: '${namePrefix}vnet${uniqueString(guid)}' } // Resource Group @@ -61,6 +64,16 @@ resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { tags: tags } +module virtualNetwork 'modules/virtualNetwork.bicep' = { + name: 'virtualNetworkModule' + scope: resourceGroup + params: { + private: private + location: location + virtualNetworkName: resourceNames.virtualNetwork + } +} + // Log Analytics Workspace module logAnalyticsWorkspace './modules/logAnalyticsWorkspace.bicep' ={ name: 'logAnalyticsWorkspaceModule' @@ -94,6 +107,8 @@ module keyVault './modules/keyVault.bicep' = { engineAppId: engineAppId engineAppSecret: engineAppSecret workspaceId: logAnalyticsWorkspace.outputs.workspaceId + privateEndpointSubnetId: virtualNetwork.outputs.privateEndpointSubnetId + private: private } } @@ -109,6 +124,8 @@ module cosmos './modules/cosmos.bicep' = { keyVaultName: keyVault.outputs.keyVaultName workspaceId: logAnalyticsWorkspace.outputs.workspaceId principalId: managedIdentity.outputs.principalId + privateEndpointSubnetId: virtualNetwork.outputs.privateEndpointSubnetId + private: private } } @@ -120,6 +137,8 @@ module storageAccount './modules/storageAccount.bicep' = if (deployAsFunc) { location: location storageAccountName: resourceNames.storageAccountName workspaceId: logAnalyticsWorkspace.outputs.workspaceId + privateEndpointSubnetId: virtualNetwork.outputs.privateEndpointSubnetId + private: private } } @@ -131,6 +150,8 @@ module containerRegistry './modules/containerRegistry.bicep' = if (privateAcr) { location: location containerRegistryName: resourceNames.containerRegistryName principalId: managedIdentity.outputs.principalId + privateEndpointSubnetId: virtualNetwork.outputs.privateEndpointSubnetId + private: private } } @@ -153,6 +174,9 @@ module appService './modules/appService.bicep' = if (!deployAsFunc) { deployAsContainer: deployAsContainer privateAcr: privateAcr privateAcrUri: privateAcr ? containerRegistry.outputs.acrUri : '' + privateEndpointSubnetId: virtualNetwork.outputs.privateEndpointSubnetId + egressSubnetId: virtualNetwork.outputs.egressSubnetId + private: private } } @@ -176,12 +200,14 @@ module functionApp './modules/functionApp.bicep' = if (deployAsFunc) { deployAsContainer: deployAsContainer privateAcr: privateAcr privateAcrUri: privateAcr ? containerRegistry.outputs.acrUri : '' + privateEndpointSubnetId: virtualNetwork.outputs.privateEndpointSubnetId + egressSubnetId: virtualNetwork.outputs.egressSubnetId + private: private } } // Outputs output suffix string = uniqueString(guid) -output subscriptionId string = subscription().subscriptionId output resourceGroupName string = resourceGroup.name output appServiceName string = deployAsFunc ? resourceNames.functionName : resourceNames.appServiceName output appServiceHostName string = deployAsFunc ? functionApp.outputs.functionAppHostName : appService.outputs.appServiceHostName diff --git a/deploy/modules/appService.bicep b/deploy/modules/appService.bicep index ed24516..06c1be2 100644 --- a/deploy/modules/appService.bicep +++ b/deploy/modules/appService.bicep @@ -40,16 +40,13 @@ param privateAcr bool @description('Uri for Private Container Registry') param privateAcrUri string +param privateEndpointSubnetId string +param egressSubnetId string +param private bool + // ACR Uri Variable var acrUri = privateAcr ? privateAcrUri : 'azureipam.azurecr.io' -// Disable Build Process Internet-Restricted Clouds -var runFromPackage = azureCloud == 'AZURE_US_GOV_SECRET' ? true : false - -// Current Python Version -var engineVersion = loadJsonContent('../../engine/app/version.json') -var pythonVersion = engineVersion.python - resource appServicePlan 'Microsoft.Web/serverfarms@2021-02-01' = { name: appServicePlanName location: location @@ -65,7 +62,7 @@ resource appServicePlan 'Microsoft.Web/serverfarms@2021-02-01' = { } } -resource appService 'Microsoft.Web/sites@2021-02-01' = { +resource appService 'Microsoft.Web/sites@2023-01-01' = { name: appServiceName location: location kind: deployAsContainer ? 'app,linux,container' : 'app,linux' @@ -83,8 +80,8 @@ resource appService 'Microsoft.Web/sites@2021-02-01' = { acrUseManagedIdentityCreds: privateAcr ? true : false acrUserManagedIdentityID: privateAcr ? managedIdentityClientId : null alwaysOn: true - linuxFxVersion: deployAsContainer ? 'DOCKER|${acrUri}/ipam:latest' : 'PYTHON|${pythonVersion}' - appCommandLine: !deployAsContainer ? 'bash ./init.sh 8000' : null + linuxFxVersion: deployAsContainer ? 'DOCKER|${acrUri}/ipam:latest' : 'PYTHON|3.9' + appCommandLine: !deployAsContainer ? 'init.sh 8000' : null healthCheckPath: '/api/status' appSettings: concat( [ @@ -142,11 +139,6 @@ resource appService 'Microsoft.Web/sites@2021-02-01' = { name: 'DOCKER_REGISTRY_SERVER_URL' value: privateAcr ? 'https://${privateAcrUri}' : 'https://index.docker.io/v1' } - ] : runFromPackage ? [ - { - name: 'WEBSITE_RUN_FROM_PACKAGE' - value: '1' - } ] : [ { name: 'SCM_DO_BUILD_DURING_DEPLOYMENT' @@ -155,6 +147,29 @@ resource appService 'Microsoft.Web/sites@2021-02-01' = { ] ) } + virtualNetworkSubnetId: egressSubnetId + publicNetworkAccess: private ? 'Disabled' : 'Enabled' + } +} + +resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-06-01' = if(private) { + name: '${appServiceName}-privateEndpoint' + location: location + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${appServiceName}-privateEndpoint' + properties: { + privateLinkServiceId: appService.id + groupIds: [ + 'sites' + ] + } + } + ] } } diff --git a/deploy/modules/containerRegistry.bicep b/deploy/modules/containerRegistry.bicep index a225222..2189eaa 100644 --- a/deploy/modules/containerRegistry.bicep +++ b/deploy/modules/containerRegistry.bicep @@ -10,15 +10,43 @@ param principalId string @description('Role Assignment GUID') param roleAssignmentName string = newGuid() +param privateEndpointSubnetId string +param private bool + var acrPull = '7f951dda-4ed3-4680-a7ca-43fe172d538d' var acrPullId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', acrPull) -resource containerRegistry 'Microsoft.ContainerRegistry/registries@2021-12-01-preview' = { +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' = { name: containerRegistryName location: location sku: { name: 'Standard' } + properties: { + adminUserEnabled: false + publicNetworkAccess: private ? 'Disabled' : 'Enabled' + } +} + +resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-06-01' = if(private) { + name: '${containerRegistryName}-privateEndpoint' + location: location + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${containerRegistryName}-privateLinkServiceConnection' + properties: { + privateLinkServiceId: containerRegistry.id + groupIds: [ + 'registry' + ] + } + } + ] + } } resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { diff --git a/deploy/modules/cosmos.bicep b/deploy/modules/cosmos.bicep index ed2b893..6800379 100644 --- a/deploy/modules/cosmos.bicep +++ b/deploy/modules/cosmos.bicep @@ -19,6 +19,9 @@ param workspaceId string @description('Managed Identity PrincipalId') param principalId string +param privateEndpointSubnetId string +param private bool + 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) @@ -40,6 +43,7 @@ resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2021-04-15' = { databaseAccountOfferType: 'Standard' enableAutomaticFailover: true disableKeyBasedMetadataWriteAccess: true + publicNetworkAccess: private ? 'Disabled' : 'Enabled' } } @@ -168,4 +172,25 @@ resource sqlRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignm } } +resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-06-01' = if(private) { + name: '${cosmosAccountName}-privateEndpoint' + location: location + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${cosmosAccountName}-privateLinkServiceConnection' + properties: { + privateLinkServiceId: cosmosAccount.id + groupIds: [ + 'sql' + ] + } + } + ] + } +} + output cosmosDocumentEndpoint string = cosmosAccount.properties.documentEndpoint diff --git a/deploy/modules/functionApp.bicep b/deploy/modules/functionApp.bicep index 05650e9..70000a0 100644 --- a/deploy/modules/functionApp.bicep +++ b/deploy/modules/functionApp.bicep @@ -43,16 +43,13 @@ param privateAcr bool @description('Uri for Private Container Registry') param privateAcrUri string +param privateEndpointSubnetId string +param egressSubnetId string +param private bool + // ACR Uri Variable var acrUri = privateAcr ? privateAcrUri : 'azureipam.azurecr.io' -// Disable Build Process Internet-Restricted Clouds -var runFromPackage = azureCloud == 'AZURE_US_GOV_SECRET' ? true : false - -// Current Python Version -var engineVersion = loadJsonContent('../../engine/app/version.json') -var pythonVersion = engineVersion.python - resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' existing = { name: storageAccountName } @@ -87,7 +84,7 @@ resource functionApp 'Microsoft.Web/sites@2021-03-01' = { siteConfig: { acrUseManagedIdentityCreds: privateAcr ? true : false acrUserManagedIdentityID: privateAcr ? managedIdentityClientId : null - linuxFxVersion: deployAsContainer ? 'DOCKER|${acrUri}/ipamfunc:latest' : 'PYTHON|${pythonVersion}' + linuxFxVersion: deployAsContainer ? 'DOCKER|${acrUri}/ipamfunc:latest' : 'Python|3.9' healthCheckPath: '/api/status' appSettings: concat( [ @@ -165,15 +162,6 @@ resource functionApp 'Microsoft.Web/sites@2021-03-01' = { name: 'WEBSITES_ENABLE_APP_SERVICE_STORAGE' value: 'false' } - ] : runFromPackage ? [ - { - name: 'FUNCTIONS_WORKER_RUNTIME' - value: 'python' - } - { - name: 'WEBSITE_RUN_FROM_PACKAGE' - value: '1' - } ] : [ { name: 'FUNCTIONS_WORKER_RUNTIME' @@ -186,6 +174,31 @@ resource functionApp 'Microsoft.Web/sites@2021-03-01' = { ] ) } + virtualNetworkSubnetId: egressSubnetId + + } +} + +resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-06-01' = if(private) { + name: '${functionAppName}-privateEndpoint' + location: location + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: 'privateLinkServiceConnection' + properties: { + privateLinkServiceId: functionApp.id + groupIds: [ + 'blob' + 'queue' + 'table' + ] + } + } + ] } } @@ -248,7 +261,7 @@ resource diagnosticSettingsApp 'Microsoft.Insights/diagnosticSettings@2021-05-01 enabled: true retentionPolicy: { days: 0 - enabled: false + enabled: false } } ] diff --git a/deploy/modules/keyVault.bicep b/deploy/modules/keyVault.bicep index caba133..f6c123d 100644 --- a/deploy/modules/keyVault.bicep +++ b/deploy/modules/keyVault.bicep @@ -26,6 +26,9 @@ param engineAppSecret string @description('Log Analytics Worskpace ID') param workspaceId string +param privateEndpointSubnetId string +param private bool + var keyVaultUser = '4633458b-17de-408a-b874-0445c86b69e6' var keyVaultUserId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', keyVaultUser) var keyVaultUserRoleAssignmentId = guid(keyVaultUser, identityPrincipalId, keyVault.id) @@ -126,5 +129,26 @@ resource keyVaultUserAssignment 'Microsoft.Authorization/roleAssignments@2020-04 } } +resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-06-01' = if(private) { + name: '${keyVaultName}-privateEndpoint' + location: location + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: 'privateLinkServiceConnection' + properties: { + privateLinkServiceId: keyVault.id + groupIds: [ + 'vault' + ] + } + } + ] + } +} + output keyVaultName string = keyVault.name output keyVaultUri string = keyVault.properties.vaultUri diff --git a/deploy/modules/storageAccount.bicep b/deploy/modules/storageAccount.bicep index 8ea0c42..3d80781 100644 --- a/deploy/modules/storageAccount.bicep +++ b/deploy/modules/storageAccount.bicep @@ -7,6 +7,9 @@ param storageAccountName string @description('Log Analytics Workspace ID') param workspaceId string +param privateEndpointSubnetId string +param private bool + resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = { name: storageAccountName location: location @@ -17,6 +20,7 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = { properties: { accessTier: 'Hot' allowBlobPublicAccess: false + publicNetworkAccess: private ? 'Disabled' : 'Enabled' } } @@ -53,7 +57,7 @@ resource diagnosticSettingsBlob 'Microsoft.Insights/diagnosticSettings@2021-05-0 enabled: true retentionPolicy: { days: 0 - enabled: false + enabled: false } } { @@ -61,7 +65,7 @@ resource diagnosticSettingsBlob 'Microsoft.Insights/diagnosticSettings@2021-05-0 enabled: true retentionPolicy: { days: 0 - enabled: false + enabled: false } } { @@ -69,7 +73,7 @@ resource diagnosticSettingsBlob 'Microsoft.Insights/diagnosticSettings@2021-05-0 enabled: true retentionPolicy: { days: 0 - enabled: false + enabled: false } } ] @@ -87,4 +91,25 @@ resource diagnosticSettingsBlob 'Microsoft.Insights/diagnosticSettings@2021-05-0 } } +resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-06-01' = if(private) { + name: '${storageAccountName}-privateEndpoint' + location: location + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: 'privateLinkServiceConnection' + properties: { + privateLinkServiceId: storageAccount.id + groupIds: [ + 'blob' + ] + } + } + ] + } +} + output name string = storageAccount.name diff --git a/deploy/modules/virtualNetwork.bicep b/deploy/modules/virtualNetwork.bicep new file mode 100644 index 0000000..9ae2f93 --- /dev/null +++ b/deploy/modules/virtualNetwork.bicep @@ -0,0 +1,44 @@ +param virtualNetworkName string +param location string +param private bool + +var addresSpace = '10.0.0.0/16' + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-06-01' = if(private) { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + addresSpace + ] + } + } +} + +resource privateEndpointSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-06-01' = if(private) { + name: 'default' + parent: virtualNetwork + properties: { + addressPrefix: cidrSubnet(addresSpace, 26, 0) + } +} + +resource egressSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-06-01' = if(private) { + name: 'egress' + parent: virtualNetwork + properties: { + addressPrefix: cidrSubnet(addresSpace, 26, 1) + delegations: [ + { + name: 'Microsoft.Web/serverFarms' + properties: { + serviceName: 'Microsoft.Web/serverFarms' + } + } + ] + } +} + +output privateEndpointSubnetId string = privateEndpointSubnet.id +output egressSubnetId string = egressSubnet.id