diff --git a/infra/core/compute/ubuntu-jumpbox.bicep b/infra/core/compute/ubuntu-jumpbox.bicep index f9e43a9b..342bbce5 100644 --- a/infra/core/compute/ubuntu-jumpbox.bicep +++ b/infra/core/compute/ubuntu-jumpbox.bicep @@ -46,19 +46,13 @@ param name string @description('The name of the linux PC. By default, this will be automatically constructed by the resource name.') param computerLinuxName string? -@description('Username for the Virtual Machine.') -param adminUsername string +@description('Username for the Virtual Machine. NOTE: this is not saved anywhere as Entra is used to manage SSH logins once created.') +param adminUsername string = newGuid() -@description('Type of authentication to use on the Virtual Machine. SSH key is recommended.') -@allowed([ - 'sshPublicKey' - 'password' -]) -param authenticationType string = 'password' - -@description('SSH Key or password for the Virtual Machine. SSH key is recommended.') +@description('Password for admin. NOTE: this is not saved anywhere as Entra is used to manage SSH logins once created.') @secure() -param adminPasswordOrKey string +@minLength(8) +param adminPassword string = newGuid() @description('The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version.') @allowed([ @@ -77,6 +71,9 @@ param vmSize string = 'Standard_B2ms' @description('The tags to associate with this resource.') param tags object = {} +@description('Users to add to VM management role for jumpbox') +param users string[] + /* ** Dependencies */ @@ -129,18 +126,7 @@ var imageReference = { version: 'latest' } } -var osDiskType = 'Standard_LRS' -var linuxConfiguration = (authenticationType == 'password') ? {} : { - disablePasswordAuthentication: true - ssh: { - publicKeys: [ - { - path: '/home/${adminUsername}/.ssh/authorized_keys' - keyData: adminPasswordOrKey - } - ] - } -} + var securityProfileJson = { uefiSettings: { secureBootEnabled: true @@ -148,12 +134,6 @@ var securityProfileJson = { } securityType: securityType } -var extensionName = 'GuestAttestation' -var extensionPublisher = 'Microsoft.Azure.Security.LinuxAttestation' -var extensionVersion = '1.0' -var maaTenantName = 'GuestAttestation' -var maaEndpoint = substring('emptystring', 0, 0) - resource networkInterface 'Microsoft.Network/networkInterfaces@2022-11-01' = { name: 'nic-${name}' @@ -177,10 +157,12 @@ resource networkInterface 'Microsoft.Network/networkInterfaces@2022-11-01' = { } } - resource virtualMachine 'Microsoft.Compute/virtualMachines@2021-11-01' = { name: name location: location + identity: { + type: 'SystemAssigned' // Needed by the Entra extension + } properties: { hardwareProfile: { vmSize: vmSize @@ -189,7 +171,7 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2021-11-01' = { osDisk: { createOption: 'FromImage' managedDisk: { - storageAccountType: osDiskType + storageAccountType: 'Standard_LRS' } } imageReference: imageReference[ubuntuOSVersion] @@ -204,29 +186,28 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2021-11-01' = { osProfile: { computerName: computerName adminUsername: adminUsername - adminPassword: adminPasswordOrKey - linuxConfiguration: ((authenticationType == 'password') ? null : linuxConfiguration) + adminPassword: adminPassword } securityProfile: ((securityType == 'TrustedLaunch') ? securityProfileJson : null) } tags: tags } -resource vmExtension 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = if ((securityType == 'TrustedLaunch') && ((securityProfileJson.uefiSettings.secureBootEnabled == true) && (securityProfileJson.uefiSettings.vTpmEnabled == true))) { +resource vmExtension 'Microsoft.Compute/virtualMachines/extensions@2023-09-01' = if ((securityType == 'TrustedLaunch') && ((securityProfileJson.uefiSettings.secureBootEnabled == true) && (securityProfileJson.uefiSettings.vTpmEnabled == true))) { parent: virtualMachine - name: extensionName + name: 'GuestAttestation' location: location properties: { - publisher: extensionPublisher - type: extensionName - typeHandlerVersion: extensionVersion + publisher: 'Microsoft.Azure.Security.LinuxAttestation' + type: 'GuestAttestation' + typeHandlerVersion: '1.0' autoUpgradeMinorVersion: true enableAutomaticUpgrade: true settings: { AttestationConfig: { MaaSettings: { - maaEndpoint: maaEndpoint - maaTenantName: maaTenantName + maaEndpoint: substring('emptystring', 0, 0) + maaTenantName: 'GuestAttestation' } } } @@ -243,7 +224,7 @@ resource postDeploymentScript 'Microsoft.Compute/virtualMachines/extensions@2023 typeHandlerVersion: '2.1' autoUpgradeMinorVersion: true settings: { - skipDos2Unix:false + skipDos2Unix: false } protectedSettings: { commandToExecute: 'chmod +x post-deployment.sh && bash post-deployment.sh' @@ -254,6 +235,31 @@ resource postDeploymentScript 'Microsoft.Compute/virtualMachines/extensions@2023 } } +resource aadsshlogin 'Microsoft.Compute/virtualMachines/extensions@2023-09-01' = { + name: 'aadsshlogin' + location: location + parent: virtualMachine + properties: { + publisher: 'Microsoft.Azure.ActiveDirectory' + type: 'AADSSHLoginForLinux' + typeHandlerVersion: '1.0' + autoUpgradeMinorVersion: true + } +} + +// Role id for Virtual Machine Administrator Login +var vmAdminLoginId = '1c0163c0-47e6-4577-8991-ea5c82e286e4' + +resource vmAdminRoles 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for user in users: { + name: guid(virtualMachine.name, resourceGroup().name, user) + scope: virtualMachine + properties: { + principalType: 'User' + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', vmAdminLoginId) + principalId: user + } +}] + resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (diagnosticSettings != null && !empty(logAnalyticsWorkspaceId)) { name: '${name}-diagnostics' scope: virtualMachine diff --git a/infra/main.bicep b/infra/main.bicep index a08474a8..c9c9827b 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -314,8 +314,6 @@ module hubNetwork './modules/hub-network.bicep' = if (willDeployHubNetwork) { logAnalyticsWorkspaceId: azureMonitor.outputs.log_analytics_workspace_id // Settings - administratorPassword: jumpboxAdministratorPassword - administratorUsername: administratorUsername createDevopsSubnet: true enableBastionHost: true // DDoS protection is recommended for Production deployments @@ -485,14 +483,11 @@ module application2 './modules/application-resources.bicep' = if (isMultiLocati module applicationPostConfiguration './modules/application-post-config.bicep' = { name: '${prefix}-application-postconfig' params: { - deploymentSettings: primaryDeploymentSettings - administratorPassword: jumpboxAdministratorPassword administratorUsername: administratorUsername databasePassword: databasePassword keyVaultName: isNetworkIsolated? hubNetwork.outputs.key_vault_name : application.outputs.key_vault_name kvResourceGroupName: isNetworkIsolated? resourceGroups.outputs.hub_resource_group_name : resourceGroups.outputs.application_resource_group_name readerIdentities: union(application.outputs.service_managed_identities, defaultDeploymentSettings.isMultiLocationDeployment ? application2.outputs.service_managed_identities : []) - resourceNames: naming.outputs.resourceNames } } diff --git a/infra/modules/application-post-config.bicep b/infra/modules/application-post-config.bicep index 0fc79ae4..f3f282e5 100644 --- a/infra/modules/application-post-config.bicep +++ b/infra/modules/application-post-config.bicep @@ -61,17 +61,6 @@ type DeploymentSettings = { // PARAMETERS // ======================================================================== -@description('The deployment settings to use for this deployment.') -param deploymentSettings DeploymentSettings - -/* -** Passwords - specify these! -*/ -@secure() -@minLength(12) -@description('The password for the administrator account. This will be used for the jump box, SQL server, and anywhere else a password is needed for creating a resource.') -param administratorPassword string = newGuid() - @minLength(8) @description('The username for the administrator account on the jump box.') param administratorUsername string = 'adminuser' @@ -81,9 +70,6 @@ param administratorUsername string = 'adminuser' @description('The password for the administrator account on the SQL Server.') param databasePassword string -@description('The resource names for the resources to be created.') -param resourceNames object - /* ** Dependencies */ @@ -141,19 +127,6 @@ resource existingKeyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = { // AZURE MODULES // ======================================================================== -module writeJumpBoxCredentialsToKeyVault '../core/security/key-vault-secrets.bicep' = if (deploymentSettings.isNetworkIsolated) { - name: 'hub-write-jumpbox-credentials-${deploymentSettings.resourceToken}' - scope: existingKvResourceGroup - params: { - name: existingKeyVault.name - secrets: [ - { key: 'Jumpbox--AdministratorPassword', value: administratorPassword } - { key: 'Jumpbox--AdministratorUsername', value: administratorUsername } - { key: 'Jumpbox--ComputerName', value: resourceNames.hubJumpbox } - ] - } -} - module writeSqlAdminInfoToKeyVault '../core/security/key-vault-secrets.bicep' = { name: 'write-sql-admin-info-to-keyvault' scope: existingKvResourceGroup diff --git a/infra/modules/hub-network.bicep b/infra/modules/hub-network.bicep index 5961fd24..5085d8da 100644 --- a/infra/modules/hub-network.bicep +++ b/infra/modules/hub-network.bicep @@ -93,14 +93,6 @@ param logAnalyticsWorkspaceId string = '' /* ** Settings */ -@secure() -@minLength(8) -@description('The password for the administrator account on the jump box.') -param administratorPassword string = newGuid() - -@minLength(8) -@description('The username for the administrator account on the jump box.') -param administratorUsername string = 'adminuser' @description('If enabled, an Ubuntu jump box will be deployed. Ensure you enable the bastion host as well.') param enableJumpBox bool = false @@ -377,9 +369,10 @@ module jumpbox '../core/compute/ubuntu-jumpbox.bicep' = if (enableJumpBox) { logAnalyticsWorkspaceId: logAnalyticsWorkspaceId subnetId: virtualNetwork.outputs.subnets[resourceNames.spokeDevopsSubnet].id + // users + users: [ deploymentSettings.principalId ] + // Settings - adminPasswordOrKey: administratorPassword - adminUsername: administratorUsername diagnosticSettings: diagnosticSettings } } diff --git a/prod-deployment.md b/prod-deployment.md index 8969bd68..03d3bd80 100644 --- a/prod-deployment.md +++ b/prod-deployment.md @@ -166,22 +166,29 @@ To retrieve the generated password: azd package ``` -1. From PowerShell use the following SCP command to upload the code to the jump box (use the password you retrieved from Key Vault to authenticate the SCP command): - ```shell - scp -r -P 50022 * azureadmin@127.0.0.1:/home/azureadmin/web-app-pattern +1. Install the SSH extension for Azure CLI + + ```pwsh + az extension add --name ssh ``` - > If you were unable to connect due to [Remote host identification has changed](troubleshooting.md#remote-host-identification-has-changed) +1. Obtain an SSH key from entra: + + ```pwsh + az ssh config --ip 127.0.0.1 -f ./ssh-config + ``` -1. From PowerShell use the SCP command to upload the AZD environment to the jump box: +1. From PowerShell use the following SCP command to upload the code to the jump box (use the password you retrieved from Key Vault to authenticate the SCP command): ```shell - scp -r -P 50022 ./.azure azureadmin@127.0.0.1:/home/azureadmin/web-app-pattern + rsync -av -e "ssh -F ./ssh-config -p 50022" . 127.0.0.1:~/web-app-pattern ``` + > If you were unable to connect due to [Remote host identification has changed](troubleshooting.md#remote-host-identification-has-changed) + 1. Run the following command to start a shell session on the jump box: ```shell - ssh azureadmin@127.0.0.1 -p 50022 + ssh -F ./ssh-config 127.0.0.1 -p 50022 ``` ### 4. Deploy code from the jump box @@ -249,7 +256,7 @@ To retrieve the generated password: 1. Use the URL displayed in the console output to launch the Relecloud application that you have deployed: ![screenshot of Relecloud app home page](assets/images/WebAppHomePage.png) - + ### 5. Teardown 1. Close the PowerShell session on the jump box: