public Infrastructure() { var currentConfig = Output.Create(GetClientConfig.InvokeAsync()); var currentUserObjectId = currentConfig.Apply(c => c.ObjectId); var tenantId = currentConfig.Apply(c => c.TenantId); var resourceGroup = new ResourceGroupResource(); var keyVault = new KeyVaultResource(resourceGroup, tenantId); var appServicePlan = new AppServicePlanResource(resourceGroup); appServicePlan.Build(); var appService = new AppServiceResource(resourceGroup); // key vault needs to be build before any resource that accesses its secrets keyVault.Build(); appService.AddConfiguration(keyVault, new[] { "KeyVaultName" }); var staticWebsite = new StorageAccountResource(resourceGroup, "sw"); staticWebsite.Build(); staticWebsite.BuildStaticWebsite(); var storageAccount = new StorageAccountResource(resourceGroup); storageAccount.Build(); var applicationInsights = new ApplicationInsightsResource(resourceGroup); applicationInsights.Build(); appService.AddConfiguration(applicationInsights, new[] { "APPINSIGHTS_INSTRUMENTATIONKEY" }); var sqlDatabase = new SqlServerResource(resourceGroup, tenantId, currentUserObjectId); sqlDatabase.Build(); keyVault.AddSecrets(sqlDatabase); appService.AddConfiguration(sqlDatabase, new[] { "DatabaseConnectionString" }); appService.Build(appServicePlan); keyVault.AddAccessPolicy("appservice", appService.PrincipalId); var activeDirectory = new ActiveDirectoryResource(currentUserObjectId); var serviceGroup = activeDirectory.CreateGroup("servicegroup", appService.PrincipalId); ServicesGroupSid = GetDatabaseSid(serviceGroup.ObjectId); }
public KeyVaultStack() { // Get current Subscription var currentSubscription = Output.Create(GetSubscription.InvokeAsync()); var tenantId = currentSubscription.Apply(currentSubscription => currentSubscription.TenantId); // Get current Client Config var currentClient = Output.Create(GetClientConfig.InvokeAsync()); var objectId = currentClient.Apply(currentClient => currentClient.ObjectId); var config = new Pulumi.Config(); var resourceGroupName = config.Require("resource_group_name"); var keyVaultName = config.Require("key_vault_name"); // Create an Azure Resource Group var resourceGroup = new ResourceGroup(resourceGroupName); // Create an Azure Key Vault var keyVault = new KeyVault(keyVaultName, new KeyVaultArgs { ResourceGroupName = resourceGroup.Name, SkuName = "standard", TenantId = tenantId, AccessPolicies = { new KeyVaultAccessPolicyArgs { TenantId = tenantId, ObjectId = objectId, SecretPermissions ={ "list", "get" } } } }); this.VaultUri = keyVault.VaultUri; }
public WebAppStack() { var resourceGroup = new ResourceGroup("rg-easy-azure-webapp"); var clientConfig = Output.Create(GetClientConfig.InvokeAsync()); var tenantId = clientConfig.Apply(config => config.TenantId); var currentPrincipal = clientConfig.Apply(config => config.ObjectId); var solutionRoot = System.Environment.GetEnvironmentVariable("SOLUTION_ROOT_DIRECTORY"); var storageAccount = new Account("storage", new AccountArgs { ResourceGroupName = resourceGroup.Name, AccountReplicationType = "LRS", AccountTier = "Standard", }); var storageContainer = new Container("files", new ContainerArgs { StorageAccountName = storageAccount.Name, ContainerAccessType = "private", }); var codeBlob = new Blob("zip", new BlobArgs { StorageAccountName = storageAccount.Name, StorageContainerName = storageContainer.Name, Type = "Block", Source = new FileArchive(Path.Join(solutionRoot, "src/Services/EasyAzureWebApp/bin/Debug/netcoreapp3.1/publish")) }); var keyVault = new KeyVault("key-vault", new KeyVaultArgs { ResourceGroupName = resourceGroup.Name, SkuName = "standard", TenantId = tenantId, SoftDeleteEnabled = true, }); var keyVaultPolicy = new AccessPolicy("key-vault-policy", new AccessPolicyArgs { KeyVaultId = keyVault.Id, TenantId = tenantId, ObjectId = currentPrincipal, SecretPermissions = new[] { "delete", "get", "list", "set" }, }); var codeBlobSecret = new Secret("zip-secret", new SecretArgs { KeyVaultId = keyVault.Id, Value = SharedAccessSignature.SignedBlobReadUrl(codeBlob, storageAccount), }); var codeBlobSecretUrl = Output.All(keyVault.VaultUri, codeBlobSecret.Name, codeBlobSecret.Version) .Apply(d => $"{d[0]}secrets/{d[1]}/{d[2]}"); var appServicePlan = new Plan("easy-azure-webapp-plan", new PlanArgs { ResourceGroupName = resourceGroup.Name, Kind = "App", Sku = new PlanSkuArgs { Tier = "Basic", Size = "B1", }, }); var appService = new AppService("easy-azure-webapp", new AppServiceArgs { ResourceGroupName = resourceGroup.Name, AppServicePlanId = appServicePlan.Id, Identity = new AppServiceIdentityArgs { Type = "SystemAssigned", }, AppSettings = new InputMap <string> { { "WEBSITE_RUN_FROM_ZIP", codeBlobSecretUrl.Apply(url => $"@Microsoft.KeyVault(SecretUri={url})") }, } }); var appServiceGet = AppService.Get("easy-azure-webapp-get", appService.Id, new AppServiceState { ResourceGroupName = resourceGroup.Name, AppServicePlanId = appServicePlan.Id, }, new CustomResourceOptions { DependsOn = appService, } ); var principalId = appServiceGet.Identity.Apply(id => id.PrincipalId !); var policy = new AccessPolicy("app-policy", new AccessPolicyArgs { KeyVaultId = keyVault.Id, TenantId = tenantId, ObjectId = principalId, SecretPermissions = "get", }); var subscriptionOutput = Output.Create(GetSubscription.InvokeAsync()); var scope = Output.All( subscriptionOutput.Apply(s => s.SubscriptionId), resourceGroup.Name, storageAccount.Name, storageContainer.Name ).Apply(s => $"/subscriptions/{s[0]}/resourcegroups/{s[1]}/providers/Microsoft.Storage/storageAccounts/{s[2]}/blobServices/default/containers/{s[3]}"); var codeBlobPermission = new Assignment("read-code-blob", new AssignmentArgs { PrincipalId = principalId !, Scope = scope, RoleDefinitionName = "Storage Blob Data Reader", },
static async Task <Dictionary <string, object?> > CreateResources() { var clientConfig = await GetClientConfig.InvokeAsync(); var tenantId = clientConfig.TenantId; var resourceGroup = new ResourceGroup($"{ NamePrefix }-group"); var kv = new KeyVault($"{ NamePrefix }-vault", new KeyVaultArgs { ResourceGroupName = resourceGroup.Name, SkuName = "standard", TenantId = tenantId, AccessPolicies = { new KeyVaultAccessPolicyArgs { TenantId = tenantId, // TODO: CHANGE ME! // The current principal has to be granted permissions to Key Vault so that it can actually add and then remove // secrets to/from the Key Vault. Otherwise, 'pulumi up' and 'pulumi destroy' operations will fail.- // // NOTE: This object ID value is NOT what you see in the Azure AD's App Registration screen. // Run `az ad sp show` from the Azure CLI to list the correct Object ID to use here. ObjectId = "your-SP-object-ID", SecretPermissions = new InputList <string>{ "delete", "get", "list", "set" }, } } }); var twilioSecret = new Secret($"{ NamePrefix }-twil", new SecretArgs { KeyVaultId = kv.Id, Value = TwilioAccountToken, }); var appInsights = new Insights($"{ NamePrefix }-ai", new InsightsArgs { ApplicationType = "web", ResourceGroupName = resourceGroup.Name, }); var durableFunctionApp = new ArchiveFunctionApp($"{ NamePrefix }-funcs", new ArchiveFunctionAppArgs { ResourceGroupName = resourceGroup.Name, Archive = new FileArchive($"./bin/Debug/netcoreapp3.1/GarageDoorMonitor/publish"), AppSettings = new InputMap <string> { { "runtime", "dotnet" }, { "FUNCTIONS_EXTENSION_VERSION", "~3" }, { "TwilioAccountToken", Output.Format($"@Microsoft.KeyVault(SecretUri ={ twilioSecret.Id })") }, { "APPINSIGHTS_INSTRUMENTATIONKEY", Output.Format($"{ appInsights.InstrumentationKey }") }, { "TimerDelayMinutes", GetIntConfigOrDefault("timerDelayMinutes", 2) }, }, HttpsOnly = true, Identity = new FunctionAppIdentityArgs { Type = "SystemAssigned" }, }); // Now that the app is created, update the access policies of the keyvault and // grant the principalId of the function app access to the vault. var principalId = durableFunctionApp.FunctionApp.Identity.Apply(id => id.PrincipalId ?? "0c4825d9-3901-40a8-ab89-ad4e3aeeadd9"); // Grant App Service access to KV secrets var appAccessPolicy = new AccessPolicy($"{ NamePrefix }-app-policy", new AccessPolicyArgs { KeyVaultId = kv.Id, TenantId = tenantId, ObjectId = principalId, SecretPermissions = new InputList <string> { "get" }, }); return(new Dictionary <string, object?> { { "webhookUrl", durableFunctionApp.Endpoint }, }); }
public MyStack() { var config = new Pulumi.Config(); var location = config.Get("location") ?? "WestUS"; var resourceGroup = new ResourceGroup("synapse-rg"); var storageAccount = new StorageAccount("synapsesa", new StorageAccountArgs { ResourceGroupName = resourceGroup.Name, AccessTier = AccessTier.Hot, EnableHttpsTrafficOnly = true, IsHnsEnabled = true, Kind = "StorageV2", Sku = new SkuArgs { Name = "Standard_RAGRS" }, }); var dataLakeStorageAccountUrl = Output.Format($"https://{storageAccount.Name}.dfs.core.windows.net"); var users = new BlobContainer("users", new BlobContainerArgs { ResourceGroupName = resourceGroup.Name, AccountName = storageAccount.Name, PublicAccess = PublicAccess.None }); var workspacePwd = new RandomPassword("workspace-pwd", new RandomPasswordArgs { Length = 12, }); var workspace = new Workspace("workspace", new WorkspaceArgs { ResourceGroupName = resourceGroup.Name, DefaultDataLakeStorage = new DataLakeStorageAccountDetailsArgs { AccountUrl = dataLakeStorageAccountUrl, Filesystem = "users" }, Identity = new ManagedIdentityArgs { Type = ResourceIdentityType.SystemAssigned }, SqlAdministratorLogin = "******", SqlAdministratorLoginPassword = workspacePwd.Result }); var allowAll = new IpFirewallRule("allowAll", new IpFirewallRuleArgs { ResourceGroupName = resourceGroup.Name, WorkspaceName = workspace.Name, EndIpAddress = "255.255.255.255", StartIpAddress = "0.0.0.0" }); var subscriptionId = resourceGroup.Id.Apply(id => id.Split('/')[2]); var roleDefinitionId = $"/subscriptions/{subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe"; var storageAccess = new RoleAssignment("storageAccess", new RoleAssignmentArgs { RoleAssignmentName = new RandomUuid("roleName").Result, Scope = storageAccount.Id, PrincipalId = workspace.Identity.Apply(identity => identity.PrincipalId).Apply(v => v ?? "<preview>"), PrincipalType = "ServicePrincipal", RoleDefinitionId = roleDefinitionId }); var clientConfig = Output.Create(GetClientConfig.InvokeAsync()); var userAccess = new RoleAssignment("userAccess", new RoleAssignmentArgs { RoleAssignmentName = new RandomUuid("userRoleName").Result, Scope = storageAccount.Id, PrincipalId = clientConfig.Apply(v => v.ObjectId), PrincipalType = "User", RoleDefinitionId = roleDefinitionId }); var sqlPool = new SqlPool("SQLPOOL1", new SqlPoolArgs { ResourceGroupName = resourceGroup.Name, WorkspaceName = workspace.Name, Collation = "SQL_Latin1_General_CP1_CI_AS", CreateMode = "Default", Sku = new Pulumi.AzureNative.Synapse.Inputs.SkuArgs { Name = "DW100c" }, }); var sparkPool = new BigDataPool("Spark1", new BigDataPoolArgs { ResourceGroupName = resourceGroup.Name, WorkspaceName = workspace.Name, AutoPause = new AutoPausePropertiesArgs { DelayInMinutes = 15, Enabled = true, }, AutoScale = new AutoScalePropertiesArgs { Enabled = true, MaxNodeCount = 3, MinNodeCount = 3, }, NodeCount = 3, NodeSize = "Small", NodeSizeFamily = "MemoryOptimized", SparkVersion = "2.4" }); }
public AppStack() { var config = new Config(); var resourceGroup = new ResourceGroup("rotatesecretoneset-rg"); var sqlAdminLogin = config.Get("sqlAdminLogin") ?? "sqlAdmin"; var sqlAdminPassword = new RandomUuid("sqlPassword").Result; var sqlServer = new Server("sqlServer", new ServerArgs { AdministratorLogin = sqlAdminLogin, AdministratorLoginPassword = sqlAdminPassword, ResourceGroupName = resourceGroup.Name, Version = "12.0", }); new FirewallRule("AllowAllWindowsAzureIps", new FirewallRuleArgs { ServerName = sqlServer.Name, ResourceGroupName = resourceGroup.Name, StartIpAddress = "0.0.0.0", EndIpAddress = "0.0.0.0", }); var clientConfig = Output.Create(GetClientConfig.InvokeAsync()); var tenantId = clientConfig.Apply(c => c.TenantId); var storageAccount = new StorageAccount("storageaccount", new StorageAccountArgs { Kind = "Storage", ResourceGroupName = resourceGroup.Name, Sku = new Storage.Inputs.SkuArgs { Name = "Standard_LRS" }, }); var appInsights = new Component("appInsights", new ComponentArgs { RequestSource = "IbizaWebAppExtensionCreate", ResourceGroupName = resourceGroup.Name, ApplicationType = "web", Kind = "web", Tags = { //{ "[concat('hidden-link:', resourceId('Microsoft.Web/sites', parameters('functionAppName')))]", "Resource" }, }, }); var secretName = config.Get("secretName") ?? "sqlPassword"; var appService = new AppServicePlan("functionApp-appService", new AppServicePlanArgs { ResourceGroupName = resourceGroup.Name, Sku = new SkuDescriptionArgs { Name = "Y1", Tier = "Dynamic", }, }); var storageKey = Output.Tuple(resourceGroup.Name, storageAccount.Name).Apply(v => { var task = ListStorageAccountKeys.InvokeAsync(new ListStorageAccountKeysArgs { AccountName = v.Item2, ResourceGroupName = v.Item1 }); return(Output.Create(task).Apply(t => t.Keys[0].Value)); }); var functionApp = new WebApp("functionApp", new WebAppArgs { Kind = "functionapp", ResourceGroupName = resourceGroup.Name, ServerFarmId = appService.Id, Identity = new ManagedServiceIdentityArgs { Type = ManagedServiceIdentityType.SystemAssigned }, SiteConfig = new SiteConfigArgs { AppSettings = { new NameValuePairArgs { Name = "AzureWebJobsStorage", Value = Output.Format($"DefaultEndpointsProtocol=https;AccountName={storageAccount.Name};AccountKey={storageKey}"), }, new NameValuePairArgs { Name = "FUNCTIONS_EXTENSION_VERSION", Value = "~3", }, new NameValuePairArgs { Name = "FUNCTIONS_WORKER_RUNTIME", Value = "dotnet", }, new NameValuePairArgs { Name = "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", Value = Output.Format($"DefaultEndpointsProtocol=https;AccountName={storageAccount.Name};EndpointSuffix=core.windows.net;AccountKey={storageKey}"), }, new NameValuePairArgs { Name = "WEBSITE_NODE_DEFAULT_VERSION", Value = "~10", }, new NameValuePairArgs { Name = "APPINSIGHTS_INSTRUMENTATIONKEY", Value = appInsights.InstrumentationKey, }, }, }, }); var functionAppSourceControl = new WebAppSourceControl("functionApp-sourceControl", new WebAppSourceControlArgs { Name = functionApp.Name, IsManualIntegration = true, Branch = "main", RepoUrl = config.Get("functionAppRepoURL") ?? "https://github.com/Azure-Samples/KeyVault-Rotation-SQLPassword-Csharp.git", ResourceGroupName = resourceGroup.Name, }); var webAppAppService = new AppServicePlan("webApp-appService", new AppServicePlanArgs { ResourceGroupName = resourceGroup.Name, Sku = new SkuDescriptionArgs { Name = "F1", }, }); var webApp = new WebApp("webApp", new WebAppArgs { Kind = "app", ResourceGroupName = resourceGroup.Name, ServerFarmId = webAppAppService.Id, Identity = new ManagedServiceIdentityArgs { Type = ManagedServiceIdentityType.SystemAssigned }, }); var keyVault = new Vault("keyVault", new VaultArgs { Properties = new VaultPropertiesArgs { AccessPolicies = { new AccessPolicyEntryArgs { TenantId = tenantId, ObjectId = functionApp.Identity.Apply(i => i !.PrincipalId), Permissions = new PermissionsArgs { Secrets ={ "get", "list", "set" }, }, },
public TdStack() { string stackName = Pulumi.Deployment.Instance.StackName; Config pulumiConfig = new(); var sqlDatabaseDbAdminId = pulumiConfig.Require("sql-server-td-db-admin-id"); var sqlDatabaseDbAdminPassword = pulumiConfig.RequireSecret("sql-server-td-db-admin-password"); var sqlDatabaseDbUserId = pulumiConfig.Require("sql-server-td-db-user-id"); var sqlDatabaseDbUserPassword = pulumiConfig.RequireSecret("sql-server-td-db-user-password"); var defaultEmailFrom = pulumiConfig.Require("default-email-from"); var emailServerHost = pulumiConfig.Require("email-server-host"); var emailServerPort = pulumiConfig.Require("email-server-port"); var emailServerUserName = pulumiConfig.Require("email-server-userName"); var emailServerPassword = pulumiConfig.RequireSecret("email-server-password"); var jwtSecretKey = pulumiConfig.RequireSecret("jwt-secret-key"); var azureDevOpsAgentVMIPAddress = pulumiConfig.Require("azure-dev-ops-agent-vm-ip"); ResourceGroup resourceGroup = new($"td-{stackName}", new ResourceGroupArgs { ResourceGroupName = $"td-{stackName}" }, options : new() { ImportId = $"/subscriptions/{GetClientConfig.InvokeAsync().GetAwaiter().GetResult().SubscriptionId}/resourceGroups/td-test" }); Workspace appInsightsWorkspace = new($"insights-wkspc-td-{stackName}", new() { WorkspaceName = $"insights-wkspc-td-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, RetentionInDays = 30 }); AppInsights appInsights = new($"app-insights-td-{stackName}", new() { ResourceName = $"app-insights-td-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, ApplicationType = AppInsightsWebApplicationType.Web, Kind = "web", IngestionMode = AppInsightsIngestionMode.LogAnalytics, DisableIpMasking = true, WorkspaceResourceId = Output.Tuple(resourceGroup.Name, appInsightsWorkspace.Name).Apply(t => { (string resourceGroupName, string workspaceName) = t; return(GetWorkspace.InvokeAsync(new GetWorkspaceArgs { ResourceGroupName = resourceGroupName, WorkspaceName = workspaceName })); }).Apply(workspace => workspace.Id) }); SqlServer sqlServer = new($"sql-server-td-{stackName}", new() { ServerName = $"sql-server-td-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, AdministratorLogin = sqlDatabaseDbAdminId, AdministratorLoginPassword = sqlDatabaseDbAdminPassword }); SqlDatabase sqlDatabase = new($"sql-database-td-{stackName}", new() { DatabaseName = $"sql-database-td-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, ServerName = sqlServer.Name, Sku = new SqlDatabaseSkuArgs { Tier = "Basic", Name = "Basic", Capacity = 5 } }); AppServicePlan appServicePlan = new($"app-plan-td-{stackName}", new() { Name = $"app-plan-td-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, Kind = "Linux", Reserved = true, Sku = new SkuDescriptionArgs { Tier = "Basic", Name = "B1", Size = "B1", Capacity = 1, Family = "B" } }); string vaultName = $"vault-td-{stackName}"; string sqlDatabaseConnectionStringSecretName = $"sql-connection-secret"; string blobStorageConnectionStringSecretName = $"blob-connection-secret"; string emailServerPasswordSecretName = "email-server-password-secret"; string jwtSecretKeySecretName = "jwt-secret-key-secret"; WebApp webApp = new($"app-service-td-{stackName}", new() { Name = $"app-service-td-{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, ServerFarmId = appServicePlan.Id, ClientAffinityEnabled = false, HttpsOnly = true, Identity = new WebAppManagedServiceIdentityArgs() { Type = ManagedServiceIdentityType.SystemAssigned }, SiteConfig = new SiteConfigArgs { Use32BitWorkerProcess = false, AlwaysOn = true, Http20Enabled = true, WebSocketsEnabled = true, NetFrameworkVersion = "v6.0", FtpsState = FtpsState.Disabled, LinuxFxVersion = "DOTNETCORE|6.0", AppCommandLine = "dotnet TodoTemplate.Api.dll", AppSettings = new() { new NameValuePairArgs { Name = "ApplicationInsights__InstrumentationKey", Value = appInsights.InstrumentationKey }, new NameValuePairArgs { Name = "APPINSIGHTS_INSTRUMENTATIONKEY", Value = appInsights.InstrumentationKey }, new NameValuePairArgs { Name = "ASPNETCORE_ENVIRONMENT", Value = stackName == "test" ? "Test" : "Production" }, new NameValuePairArgs { Name = "APPLICATIONINSIGHTS_CONNECTION_STRING", Value = appInsights.ConnectionString }, new NameValuePairArgs { Name = "APPINSIGHTS_PROFILERFEATURE_VERSION", Value = "disabled" }, new NameValuePairArgs { Name = "APPINSIGHTS_SNAPSHOTFEATURE_VERSION", Value = "disabled" }, new NameValuePairArgs { Name = "ApplicationInsightsAgent_EXTENSION_VERSION", Value = "~3" }, new NameValuePairArgs { Name = "XDT_MicrosoftApplicationInsights_BaseExtensions", Value = "~1" }, new NameValuePairArgs { Name = "InstrumentationEngine_EXTENSION_VERSION", Value = "~1" }, new NameValuePairArgs { Name = "SnapshotDebugger_EXTENSION_VERSION", Value = "disabled" }, new NameValuePairArgs { Name = "XDT_MicrosoftApplicationInsights_Mode", Value = "recommended" }, new NameValuePairArgs { Name = "XDT_MicrosoftApplicationInsights_PreemptSdk", Value = "disabled" }, new NameValuePairArgs { Name = "AppSettings__EmailSettings__DefaulFromEmail", Value = defaultEmailFrom }, new NameValuePairArgs { Name = "AppSettings__EmailSettings__Host", Value = emailServerHost }, new NameValuePairArgs { Name = "AppSettings__EmailSettings__Port", Value = emailServerPort }, new NameValuePairArgs { Name = "AppSettings__EmailSettings__UserName", Value = emailServerUserName }, new NameValuePairArgs { Name = "AppSettings__EmailSettings__Password", Value = $"@Microsoft.KeyVault(VaultName={vaultName};SecretName={emailServerPasswordSecretName})" }, new NameValuePairArgs { Name = "AppSettings__JwtSettings__SecretKey", Value = $"@Microsoft.KeyVault(VaultName={vaultName};SecretName={jwtSecretKeySecretName})" }, }, ConnectionStrings = new() { new ConnStringInfoArgs { Name = "SqlServerConnectionString", Type = ConnectionStringType.SQLAzure, ConnectionString = $"@Microsoft.KeyVault(VaultName={vaultName};SecretName={sqlDatabaseConnectionStringSecretName})" }, new ConnStringInfoArgs { Name = "AzureBlobStorageConnectionString", Type = ConnectionStringType.Custom, ConnectionString = $"@Microsoft.KeyVault(VaultName={vaultName};SecretName={blobStorageConnectionStringSecretName})" } } } }); StorageAccount blobStorageAccount = new($"storageacctd{stackName}", new() { AccountName = $"storageacctd{stackName}", ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, AccessTier = AccessTier.Hot, Kind = StorageKind.BlobStorage, Sku = new StorageAccountSkuArgs { Name = StorageSkuName.Standard_LRS } }); BlobContainer attachmentsContainer = new("attachments", new() { ResourceGroupName = resourceGroup.Name, AccountName = blobStorageAccount.Name, ContainerName = $"attachments", PublicAccess = PublicAccess.None }); new SqlServerFirewallRule($"fw-{stackName}-azure-dev-ops-agent-vm-ip", new() { Name = $"fw-{stackName}-azure-dev-ops-agent-vm-ip", FirewallRuleName = $"fw-{stackName}-azure-dev-ops-agent-vm-ip", EndIpAddress = azureDevOpsAgentVMIPAddress, ResourceGroupName = resourceGroup.Name, ServerName = sqlServer.Name, StartIpAddress = azureDevOpsAgentVMIPAddress }); Output.Tuple(resourceGroup.Name, webApp.Name).Apply(t => { (string resourceGroupName, string webAppName) = t; Output.Create(GetWebApp.InvokeAsync(new() { Name = webAppName, ResourceGroupName = resourceGroupName })) .Apply(webAppToGetIPAddresses => { var ipAddresses = webAppToGetIPAddresses.PossibleOutboundIpAddresses; foreach (var ipAddress in ipAddresses.Split(',')) { new SqlServerFirewallRule($"fw-{stackName}-{ipAddress}", new() { Name = $"fw-{stackName}-{ipAddress}", FirewallRuleName = $"fw-{stackName}-{ipAddress}", EndIpAddress = ipAddress, ResourceGroupName = resourceGroup.Name, ServerName = sqlServer.Name, StartIpAddress = ipAddress }); } return(string.Empty); }); return(string.Empty); }); Vault vault = new Vault($"vault-td-{stackName}", new() { ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, VaultName = vaultName, Properties = new VaultPropertiesArgs() { Sku = new VaultSkuArgs { Name = VaultSkuName.Standard, Family = SkuFamily.A }, TenantId = Output.Create(GetClientConfig.InvokeAsync()).Apply(clientConfig => clientConfig.TenantId), EnabledForDeployment = true, EnabledForDiskEncryption = true, EnabledForTemplateDeployment = true, EnableSoftDelete = false, AccessPolicies = new List <AccessPolicyEntryArgs> { new AccessPolicyEntryArgs { TenantId = Output.Create(GetClientConfig.InvokeAsync()).Apply(clientConfig => clientConfig.TenantId), ObjectId = Output.Tuple(resourceGroup.Name, webApp.Name).Apply(t => { (string resourceGroupName, string webAppName) = t; return(GetWebApp.InvokeAsync(new GetWebAppArgs { ResourceGroupName = resourceGroupName, Name = webAppName })); }).Apply(app => app.Identity !.PrincipalId), Permissions = new PermissionsArgs { Secrets = { "get" } } } } } }); Secret jwtSecretKeySecret = new(jwtSecretKeySecretName, new() { ResourceGroupName = resourceGroup.Name, VaultName = vault.Name, SecretName = jwtSecretKeySecretName, Properties = new SecretPropertiesArgs { Value = jwtSecretKey } }); Secret emailServerPasswordSecret = new(emailServerPasswordSecretName, new() { ResourceGroupName = resourceGroup.Name, VaultName = vault.Name, SecretName = emailServerPasswordSecretName, Properties = new SecretPropertiesArgs { Value = emailServerPassword } }); Secret sqlDatabaseConnectionStringSecret = new(sqlDatabaseConnectionStringSecretName, new() { ResourceGroupName = resourceGroup.Name, VaultName = vault.Name, SecretName = sqlDatabaseConnectionStringSecretName, Properties = new SecretPropertiesArgs { Value = Output.Tuple(sqlServer.Name, sqlDatabase.Name, sqlDatabaseDbUserPassword).Apply(t => { (string _sqlServer, string _sqlDatabase, string _sqlDatabasePassword) = t; return($"Data Source=tcp:{_sqlServer}.database.windows.net;Initial Catalog={_sqlDatabase};User ID={sqlDatabaseDbUserId};Password={_sqlDatabasePassword};Application Name=Todo;Encrypt=True;"); }) } }); var attachmentsContainerConnectionString = Output.Tuple(resourceGroup.Name, blobStorageAccount.Name, attachmentsContainer.Name) .Apply(t => { (string resourceGroupName, string blobStorageAccountName, string attachmentsContainerName) = t; var result = Output.Create(GetStorageAccountPrimaryKey(resourceGroupName, blobStorageAccountName)) .Apply(blobStorageAccountKey => { BlobSasBuilder blobSasBuilder = new BlobSasBuilder { BlobContainerName = attachmentsContainerName, Protocol = SasProtocol.Https, StartsOn = DateTimeOffset.Parse("2022-01-01T22:00:00Z"), ExpiresOn = DateTimeOffset.Parse("2032-01-01T22:00:00Z"), Resource = "c" }; blobSasBuilder.SetPermissions(BlobSasPermissions.Read | BlobSasPermissions.Create | BlobSasPermissions.Delete); var blobStorageConnectionString = $"https://{blobStorageAccountName}.blob.core.windows.net/{attachmentsContainerName}?{blobSasBuilder.ToSasQueryParameters(new StorageSharedKeyCredential(blobStorageAccountName, blobStorageAccountKey))}"; return(blobStorageConnectionString); }); return(result); }); Secret blobStorageConnectionStringSecret = new(blobStorageConnectionStringSecretName, new() { ResourceGroupName = resourceGroup.Name, VaultName = vault.Name, SecretName = blobStorageConnectionStringSecretName, Properties = new SecretPropertiesArgs { Value = attachmentsContainerConnectionString } }); }
public AppStack() { var resourceGroup = new ResourceGroup("keyvault-rg"); // Create a storage account for Blobs var storageAccount = new Account("storage", new AccountArgs { ResourceGroupName = resourceGroup.Name, AccountReplicationType = "LRS", AccountTier = "Standard", }); // The container to put our files into var storageContainer = new Container("files", new ContainerArgs { StorageAccountName = storageAccount.Name, ContainerAccessType = "private", }); // Azure SQL Server that we want to access from the application var administratorLoginPassword = new RandomPassword("password", new RandomPasswordArgs { Length = 16, Special = true }).Result; var sqlServer = new SqlServer("sqlserver", new SqlServerArgs { ResourceGroupName = resourceGroup.Name, // The login and password are required but won't be used in our application AdministratorLogin = "******", AdministratorLoginPassword = administratorLoginPassword, Version = "12.0", }); // Azure SQL Database that we want to access from the application var database = new Database("db", new DatabaseArgs { ResourceGroupName = resourceGroup.Name, ServerName = sqlServer.Name, RequestedServiceObjectiveName = "S0", }); // The connection string that has no credentials in it: authertication will come through MSI var connectionString = Output.Format($"Server=tcp:{sqlServer.Name}.database.windows.net;Database={database.Name};"); // A file in Blob Storage that we want to access from the application var textBlob = new Blob("text", new BlobArgs { StorageAccountName = storageAccount.Name, StorageContainerName = storageContainer.Name, Type = "Block", Source = new FileAsset("./README.md"), }); // A plan to host the App Service var appServicePlan = new Plan("asp", new PlanArgs { ResourceGroupName = resourceGroup.Name, Kind = "App", Sku = new PlanSkuArgs { Tier = "Basic", Size = "B1", }, }); // ASP.NET deployment package var blob = new Blob("zip", new BlobArgs { StorageAccountName = storageAccount.Name, StorageContainerName = storageContainer.Name, Type = "Block", Source = new FileArchive("./webapp/bin/Debug/netcoreapp3.1/publish"), }); var clientConfig = Output.Create(GetClientConfig.InvokeAsync()); var tenantId = clientConfig.Apply(c => c.TenantId); var currentPrincipal = clientConfig.Apply(c => c.ObjectId); // Key Vault to store secrets (e.g. Blob URL with SAS) var vault = new KeyVault("vault", new KeyVaultArgs { ResourceGroupName = resourceGroup.Name, SkuName = "standard", TenantId = tenantId, AccessPolicies = { new KeyVaultAccessPolicyArgs { TenantId = tenantId, // The current principal has to be granted permissions to Key Vault so that it can actually add and then remove // secrets to/from the Key Vault. Otherwise, 'pulumi up' and 'pulumi destroy' operations will fail. ObjectId = currentPrincipal, SecretPermissions ={ "delete", "get", "list", "set" }, } }, }); // Put the URL of the zip Blob to KV var secret = new Secret("deployment-zip", new SecretArgs { KeyVaultId = vault.Id, Value = SharedAccessSignature.SignedBlobReadUrl(blob, storageAccount), }); var secretUri = Output.Format($"{vault.VaultUri}secrets/{secret.Name}/{secret.Version}"); // The application hosted in App Service var app = new AppService("app", new AppServiceArgs { ResourceGroupName = resourceGroup.Name, AppServicePlanId = appServicePlan.Id, // A system-assigned managed service identity to be used for authentication and authorization to the SQL Database and the Blob Storage Identity = new AppServiceIdentityArgs { Type = "SystemAssigned" }, AppSettings = { // Website is deployed from a URL read from the Key Vault { "WEBSITE_RUN_FROM_ZIP", Output.Format($"@Microsoft.KeyVault(SecretUri={secretUri})") }, // Note that we simply provide the URL without SAS or keys { "StorageBlobUrl", textBlob.Url }, }, ConnectionStrings = { new AppServiceConnectionStringArgs { Name = "db", Type = "SQLAzure", Value = connectionString, }, }, }); // Work around a preview issue https://github.com/pulumi/pulumi-azure/issues/192 var principalId = app.Identity.Apply(id => id.PrincipalId ?? "11111111-1111-1111-1111-111111111111"); // Grant App Service access to KV secrets var policy = new AccessPolicy("app-policy", new AccessPolicyArgs { KeyVaultId = vault.Id, TenantId = tenantId, ObjectId = principalId, SecretPermissions = { "get" }, }); // Make the App Service the admin of the SQL Server (double check if you want a more fine-grained security model in your real app) var sqlAdmin = new ActiveDirectoryAdministrator("adadmin", new ActiveDirectoryAdministratorArgs { ResourceGroupName = resourceGroup.Name, TenantId = tenantId, ObjectId = principalId, Login = "******", ServerName = sqlServer.Name, }); // Grant access from App Service to the container in the storage var blobPermission = new Assignment("readblob", new AssignmentArgs { PrincipalId = principalId, Scope = Output.Format($"{storageAccount.Id}/blobServices/default/containers/{storageContainer.Name}"), RoleDefinitionName = "Storage Blob Data Reader", }); // Add SQL firewall exceptions var firewallRules = app.OutboundIpAddresses.Apply( ips => ips.Split(",").Select( ip => new FirewallRule($"FR{ip}", new FirewallRuleArgs { ResourceGroupName = resourceGroup.Name, StartIpAddress = ip, EndIpAddress = ip, ServerName = sqlServer.Name, }) ).ToList()); this.Endpoint = Output.Format($"https://{app.DefaultSiteHostname}"); }
public CdStack() { Config pulumiConfig = new(); ResourceGroup resourceGroup = new("td-cd", new ResourceGroupArgs { ResourceGroupName = "td-cd" }, options : new() { ImportId = $"/subscriptions/{GetClientConfig.InvokeAsync().GetAwaiter().GetResult().SubscriptionId}/resourceGroups/td-cd" }); var azureDevOpsVMAdminUserName = pulumiConfig.Require($"dev-ops-vm-td-admin-user-name"); var azureDevOpsVMAdminUserPassword = pulumiConfig.RequireSecret($"dev-ops-vm-td-admin-user-password"); NetworkSecurityGroup azureDevOpsAgentVMNetSecurityGroup = new($"dev-ops-net-sec-group-td", new() { ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, NetworkSecurityGroupName = $"dev-ops-net-sec-group-td", SecurityRules = new List <Pulumi.AzureNative.Network.Inputs.SecurityRuleArgs> { /*new Pulumi.AzureNative.Network.Inputs.SecurityRuleArgs * { * Name = "RDP", * Protocol = SecurityRuleProtocol.Tcp, * SourcePortRange = "*", * DestinationPortRange = "3389", * SourceAddressPrefix ="*", * DestinationAddressPrefix = "*", * Access= SecurityRuleAccess.Allow, * Priority = 300, * Direction= SecurityRuleDirection.Inbound * }*/ } }); VirtualNetwork network = new($"dev-ops-net-td", new() { ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, VirtualNetworkName = $"dev-ops-net-td", AddressSpace = new AddressSpaceArgs { AddressPrefixes = new List <string> { "10.0.0.0/16" } } }); PublicIPAddress ipAddress = new($"dev-ops-ip-td", new() { ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, PublicIpAddressName = $"dev-ops-ip-td", PublicIPAllocationMethod = IPAllocationMethod.Static, Sku = new PublicIPAddressSkuArgs { Name = Pulumi.AzureNative.Network.PublicIPAddressSkuName.Standard }, DnsSettings = new PublicIPAddressDnsSettingsArgs { DomainNameLabel = $"dev-ops-vm-td" // dev-ops-vm-td.eastus.cloudapp.azure.com } }); Subnet subnet = new Subnet($"dev-ops-subnet-td", new() { ResourceGroupName = resourceGroup.Name, Name = $"dev-ops-subnet-td", SubnetName = $"dev-ops-subnet-td", VirtualNetworkName = network.Name, AddressPrefix = "10.0.2.0/24" }); NetworkInterface networkInterface = new($"dev-ops-net-interface", new() { ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, NetworkInterfaceName = $"dev-ops-net-interface", // EnableAcceleratedNetworking = true, // Depends on your vm size DnsSettings = new NetworkInterfaceDnsSettingsArgs { InternalDnsNameLabel = $"dev-ops-vm-td" // dev-ops-vm-td.internal.cloudapp.net }, IpConfigurations = new List <NetworkInterfaceIPConfigurationArgs> { new NetworkInterfaceIPConfigurationArgs { Name = $"dev-ops-vm-ip-td", PrivateIPAddress = "10.0.0.4", PrivateIPAllocationMethod = IPAllocationMethod.Dynamic, PublicIPAddress = new Pulumi.AzureNative.Network.Inputs.PublicIPAddressArgs { Id = ipAddress.Id, IpAddress = ipAddress.IpAddress }, Subnet = new NetworkSubnetArgs { Id = subnet.Id }, Primary = true } }, NetworkSecurityGroup = new Pulumi.AzureNative.Network.Inputs.NetworkSecurityGroupArgs { Location = resourceGroup.Location, Id = azureDevOpsAgentVMNetSecurityGroup.Id } }); VirtualMachine vm = new($"dev-ops-vm-td", new() { ResourceGroupName = resourceGroup.Name, Location = resourceGroup.Location, VmName = $"dev-ops-vm-td", OsProfile = new Pulumi.AzureNative.Compute.Inputs.OSProfileArgs { AdminUsername = azureDevOpsVMAdminUserName, AdminPassword = azureDevOpsVMAdminUserPassword, ComputerName = $"dev-ops-vm-td", WindowsConfiguration = new Pulumi.AzureNative.Compute.Inputs.WindowsConfigurationArgs { ProvisionVMAgent = true, EnableAutomaticUpdates = true } }, HardwareProfile = new Pulumi.AzureNative.Compute.Inputs.HardwareProfileArgs { VmSize = VirtualMachineSizeTypes.Standard_B1ms }, StorageProfile = new Pulumi.AzureNative.Compute.Inputs.StorageProfileArgs { ImageReference = new Pulumi.AzureNative.Compute.Inputs.ImageReferenceArgs { Publisher = "MicrosoftWindowsServer", Offer = "WindowsServer", Sku = "2019-Datacenter", Version = "latest" }, OsDisk = new Pulumi.AzureNative.Compute.Inputs.OSDiskArgs { ManagedDisk = new Pulumi.AzureNative.Compute.Inputs.ManagedDiskParametersArgs { StorageAccountType = StorageAccountTypes.Standard_LRS }, OsType = OperatingSystemTypes.Windows, Name = $"dev-ops-vm-disk-td", CreateOption = DiskCreateOptionTypes.FromImage, Caching = CachingTypes.ReadWrite, DiskSizeGB = 127 } }, NetworkProfile = new Pulumi.AzureNative.Compute.Inputs.NetworkProfileArgs { NetworkInterfaces = new List <Pulumi.AzureNative.Compute.Inputs.NetworkInterfaceReferenceArgs> { new Pulumi.AzureNative.Compute.Inputs.NetworkInterfaceReferenceArgs { Id = networkInterface.Id } } } }); }