diff --git a/src/deploy-cromwell-on-azure/Deployer.cs b/src/deploy-cromwell-on-azure/Deployer.cs index 2a5edf41..774323f2 100644 --- a/src/deploy-cromwell-on-azure/Deployer.cs +++ b/src/deploy-cromwell-on-azure/Deployer.cs @@ -17,6 +17,7 @@ using Azure.ResourceManager; using Azure.ResourceManager.Network; using Azure.ResourceManager.Network.Models; +using Azure.ResourceManager.Resources; using Azure.Security.KeyVault.Secrets; using Azure.Storage; using Azure.Storage.Blobs; @@ -106,6 +107,11 @@ public class Deployer "Microsoft.DBforPostgreSQL" }; + private readonly Dictionary> requiredResourceProviderFeatures = new Dictionary>() + { + { "Microsoft.Compute", new List { "EncryptionAtHost" } } + }; + private Configuration configuration { get; set; } private ITokenProvider tokenProvider; private TokenCredentials tokenCredentials; @@ -374,7 +380,7 @@ await Execute("Connecting to Azure Services...", async () => } await RegisterResourceProvidersAsync(); - await ValidateVmAsync(); + await RegisterResourceProviderFeaturesAsync(); if (batchAccount is null) { @@ -780,6 +786,7 @@ private async Task ProvisionManagedCluster(IResource resourceGro VmSize = configuration.VmSize, OsDiskSizeGB = 128, OsDiskType = OSDiskType.Managed, + EnableEncryptionAtHost = true, Type = "VirtualMachineScaleSets", EnableAutoScaling = false, EnableNodePublicIP = false, @@ -1059,6 +1066,74 @@ private async Task> GetRequiredResourceProvidersNotRegisteredAsync( return notRegisteredResourceProviders; } + private async Task RegisterResourceProviderFeaturesAsync() + { + var unregisteredFeatures = new List(); + try + { + await Execute( + $"Registering resource provider features...", + async () => + { + var subscription = armClient.GetSubscriptionResource(new ResourceIdentifier($"/subscriptions/{configuration.SubscriptionId}")); + + foreach (var rpName in requiredResourceProviderFeatures.Keys) + { + var rp = await subscription.GetResourceProviderAsync(rpName); + + foreach (var featureName in requiredResourceProviderFeatures[rpName]) + { + var feature = await rp.Value.GetFeatureAsync(featureName); + + if (!string.Equals(feature.Value.Data.FeatureState, "Registered", StringComparison.OrdinalIgnoreCase)) + { + unregisteredFeatures.Add(feature); + _ = await feature.Value.RegisterAsync(); + } + } + } + + while (!cts.IsCancellationRequested) + { + if (unregisteredFeatures.Count == 0) + { + break; + } + + await Task.Delay(System.TimeSpan.FromSeconds(30)); + var finished = new List(); + + foreach (var feature in unregisteredFeatures) + { + var update = await feature.GetAsync(); + + if (string.Equals(update.Value.Data.FeatureState, "Registered", StringComparison.OrdinalIgnoreCase)) + { + finished.Add(feature); + } + } + unregisteredFeatures.RemoveAll(x => finished.Contains(x)); + } + }); + } + catch (Microsoft.Rest.Azure.CloudException ex) when (ex.ToCloudErrorType() == CloudErrorType.AuthorizationFailed) + { + ConsoleEx.WriteLine(); + ConsoleEx.WriteLine("Unable to programatically register the required features.", ConsoleColor.Red); + ConsoleEx.WriteLine("This can happen if you don't have the Owner or Contributor role assignment for the subscription.", ConsoleColor.Red); + ConsoleEx.WriteLine(); + ConsoleEx.WriteLine("Please contact the Owner or Contributor of your Azure subscription, and have them:", ConsoleColor.Yellow); + ConsoleEx.WriteLine(); + ConsoleEx.WriteLine("1. For each of the following, execute 'az feature register --namespace {RESOURCE_PROVIDER_NAME} --name {FEATURE_NAME}'", ConsoleColor.Yellow); + ConsoleEx.WriteLine(); + unregisteredFeatures.ForEach(f => ConsoleEx.WriteLine($"- {f.Data.Name}", ConsoleColor.Yellow)); + ConsoleEx.WriteLine(); + ConsoleEx.WriteLine("After completion, please re-attempt deployment."); + + Environment.Exit(1); + } + } + private Task AssignManagedIdOperatorToResourceAsync(IIdentity managedIdentity, IResource resource) { // https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#managed-identity-operator @@ -1327,21 +1402,27 @@ private Task AssignVmAsContributorToAppInsightsAsync(IIdentity managedIdentity, $"Creating virtual network and subnets: {configuration.VnetName}...", async () => { + var defaultNsg = await CreateNetworkSecurityGroupAsync(resourceGroup, $"{configuration.VnetName}-default-nsg"); + var vnetDefinition = azureSubscriptionClient.Networks .Define(configuration.VnetName) .WithRegion(configuration.RegionName) .WithExistingResourceGroup(resourceGroup) .WithAddressSpace(configuration.VnetAddressSpace) .DefineSubnet(configuration.VmSubnetName) - .WithAddressPrefix(configuration.VmSubnetAddressSpace).Attach(); + .WithAddressPrefix(configuration.VmSubnetAddressSpace) + .WithExistingNetworkSecurityGroup(defaultNsg) + .Attach(); vnetDefinition = vnetDefinition.DefineSubnet(configuration.PostgreSqlSubnetName) .WithAddressPrefix(configuration.PostgreSqlSubnetAddressSpace) + .WithExistingNetworkSecurityGroup(defaultNsg) .WithDelegation("Microsoft.DBforPostgreSQL/flexibleServers") .Attach(); vnetDefinition = vnetDefinition.DefineSubnet(configuration.BatchSubnetName) .WithAddressPrefix(configuration.BatchNodesSubnetAddressSpace) + .WithExistingNetworkSecurityGroup(defaultNsg) .Attach(); var vnet = await vnetDefinition.CreateAsync(); @@ -1360,6 +1441,14 @@ private Task AssignVmAsContributorToAppInsightsAsync(IIdentity managedIdentity, batchSubnet); }); + private Task CreateNetworkSecurityGroupAsync(IResourceGroup resourceGroup, string networkSecurityGroupName) + { + return azureSubscriptionClient.NetworkSecurityGroups.Define(networkSecurityGroupName) + .WithRegion(configuration.RegionName) + .WithExistingResourceGroup(resourceGroup) + .CreateAsync(cts.Token); + } + private string GetFormattedPostgresqlUser(bool isCromwellPostgresUser) { var user = isCromwellPostgresUser ? diff --git a/src/deploy-cromwell-on-azure/KubernetesManager.cs b/src/deploy-cromwell-on-azure/KubernetesManager.cs index d09d6cd3..75a74678 100644 --- a/src/deploy-cromwell-on-azure/KubernetesManager.cs +++ b/src/deploy-cromwell-on-azure/KubernetesManager.cs @@ -40,7 +40,7 @@ internal class KubernetesManager // "master" is used despite not being a best practice: https://github.com/kubernetes-sigs/blob-csi-driver/issues/783 private const string BlobCsiDriverGithubReleaseBranch = "master"; - private const string BlobCsiDriverGithubReleaseVersion = "v1.18.0"; + private const string BlobCsiDriverGithubReleaseVersion = "v1.21.4"; private const string BlobCsiRepo = $"https://raw.githubusercontent.com/kubernetes-sigs/blob-csi-driver/{BlobCsiDriverGithubReleaseBranch}/charts"; private const string AadPluginGithubReleaseVersion = "v1.8.13"; private const string AadPluginRepo = $"https://raw.githubusercontent.com/Azure/aad-pod-identity/{AadPluginGithubReleaseVersion}/charts"; diff --git a/src/ga4gh-tes b/src/ga4gh-tes index 3229c629..0165bbea 160000 --- a/src/ga4gh-tes +++ b/src/ga4gh-tes @@ -1 +1 @@ -Subproject commit 3229c629494ee35ff5ec595d8106a93433f15ac4 +Subproject commit 0165bbea44b3eb6a33dade0ad1ac73a829adbd94