Beispiel #1
0
    static Task <int> Main()
    {
        return(Deployment.RunAsync(async() => {
            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 = "./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 ZipBlob("zip", new ZipBlobArgs
            {
                StorageAccountName = storageAccount.Name,
                StorageContainerName = storageContainer.Name,
                Type = "block",
                Content = new FileArchive("./webapp/bin/Debug/netcoreapp2.2/publish"),
            });

            var clientConfig = await Pulumi.Azure.Core.Invokes.GetClientConfig();
            var tenantId = clientConfig.TenantId;
            var currentPrincipal = clientConfig.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 KeyVaultAccessPoliciesArgs
                    {
                        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($"{secret.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 AppServiceConnectionStringsArgs
                    {
                        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());

            return new Dictionary <string, object>
            {
                { "endpoint", Output.Format($"https://{app.DefaultSiteHostname}") },
            };
        }));
    }
        private static AppointmentApiAzureResourceBag CreateAppointmentApiAzureResources(AzureResourceBag azureResources, Config config, Input <string> registryImageName)
        {
            var tenantId = config.Require("azure-tenantid");

            var appointmentApiDb = new Database("appointments-api-db", new DatabaseArgs
            {
                ResourceGroupName = azureResources.ResourceGroup.Name,
                Name       = "appointment-api",
                ServerName = azureResources.SqlServer.Name,
                RequestedServiceObjectiveName = "S0",
                Tags = azureResources.Tags
            });

            var image = new Image("appointments-api-docker-image", new ImageArgs
            {
                Build    = $".{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}",
                Registry = new ImageRegistry
                {
                    Server   = azureResources.Registry.LoginServer,
                    Username = azureResources.Registry.AdminUsername,
                    Password = azureResources.Registry.AdminPassword
                },
                ImageName = registryImageName
            }, new ComponentResourceOptions
            {
                DependsOn = new InputList <Resource> {
                    azureResources.Registry
                }
            });

            var appointmentApiIdentity = new UserAssignedIdentity("appointments-api", new UserAssignedIdentityArgs
            {
                ResourceGroupName = azureResources.ResourceGroup.Name,
                Name = "appointments-api",
                Tags = azureResources.Tags
            });

            // AKS service principal needs to have Managed Identity Operator rights over the user assigned identity else AAD pod identity won't work
            var aksSpAppointmentApiAccessPolicy = new Assignment("aks-sp-appontment-api-access", new AssignmentArgs
            {
                PrincipalId        = azureResources.AksServicePrincipal.ObjectId,
                RoleDefinitionName = "Managed Identity Operator",
                Scope = appointmentApiIdentity.Id
            });

            var sqlAdmin = new ActiveDirectoryAdministrator("appointments-api-sql-access", new ActiveDirectoryAdministratorArgs
            {
                ResourceGroupName = azureResources.ResourceGroup.Name,
                TenantId          = tenantId,
                ObjectId          = appointmentApiIdentity.PrincipalId,
                Login             = "******",
                ServerName        = azureResources.SqlServer.Name
            });

            var clientConfig             = Output.Create(GetClientConfig.InvokeAsync());
            var currentPrincipalTenantId = clientConfig.Apply(c => c.TenantId);
            var currentPrincipal         = clientConfig.Apply(c => c.ObjectId);

            var appointmentApiKeyVault = new KeyVault("appointment-api-keyvault", new KeyVaultArgs
            {
                ResourceGroupName = azureResources.ResourceGroup.Name,
                Name = "appointments-api",
                EnabledForDiskEncryption = true,
                TenantId       = tenantId,
                SkuName        = "standard",
                AccessPolicies = new InputList <KeyVaultAccessPolicyArgs>
                {
                    new KeyVaultAccessPolicyArgs
                    {
                        TenantId          = tenantId,
                        ObjectId          = azureResources.AksServicePrincipal.ObjectId,
                        SecretPermissions = new[] { "get", "list" }
                    },
                    new KeyVaultAccessPolicyArgs
                    {
                        TenantId          = tenantId,
                        ObjectId          = appointmentApiIdentity.PrincipalId,
                        SecretPermissions = new[] { "get", "list" }
                    },
                    new KeyVaultAccessPolicyArgs
                    {
                        TenantId          = currentPrincipalTenantId,
                        ObjectId          = currentPrincipal,
                        SecretPermissions = { "delete", "get", "list", "set" },
                    }
                },
                NetworkAcls = new KeyVaultNetworkAclsArgs
                {
                    DefaultAction           = "Deny",
                    Bypass                  = "******",
                    VirtualNetworkSubnetIds = new InputList <string>
                    {
                        azureResources.Subnet.Id
                    },
                    // Need to whitelist the local public IP address otherwise setting secrets will fail
                    IpRules = new InputList <string>
                    {
                        GetMyPublicIpAddress()
                    }
                },
                Tags = azureResources.Tags
            });

            var secret = new Pulumi.Azure.KeyVault.Secret("appointments-api-db-connection-string",
                                                          new Pulumi.Azure.KeyVault.SecretArgs
            {
                KeyVaultId = appointmentApiKeyVault.Id,
                Name       = "ConnectionStrings--PetDoctorContext",
                Value      = Output.Tuple(azureResources.SqlServer.Name, azureResources.SqlServer.Name,
                                          azureResources.SqlServer.AdministratorLogin, azureResources.SqlServer.AdministratorLoginPassword).Apply(
                    t =>
                {
                    var(server, database, administratorLogin, administratorLoginPassword) = t;
                    return
                    ($"Server=tcp:{server}.database.windows.net;Database={database};User ID={administratorLogin};Password={administratorLoginPassword}");
                })
            }, new CustomResourceOptions
            {
                DependsOn = new InputList <Resource>
                {
                    azureResources.SqlServer,
                    appointmentApiKeyVault
                }
            });

            return(new AppointmentApiAzureResourceBag
            {
                Identity = appointmentApiIdentity,
                KeyVault = appointmentApiKeyVault
            });
        }