From 310cb87e88b2789e6121966ea07bf6129cc37b2d Mon Sep 17 00:00:00 2001 From: Alex Macocian Date: Tue, 28 Nov 2023 13:30:00 +0100 Subject: [PATCH] Setup AD based auth --- .github/workflows/content-deploy.yaml | 2 +- .github/workflows/docker-deploy.yaml | 6 ++++-- .../BuildAndUploadContent.ps1 | 17 ++++++++++++---- GuildWarsPartySearch/Config.Debug.json | 7 ++++++- GuildWarsPartySearch/Config.Release.json | 7 ++++++- .../Extensions/ServiceCollectionExtensions.cs | 16 ++++++++++----- .../WebApplicationBuilderExtensions.cs | 20 ++++++++++++++++++- .../GuildWarsPartySearch.Server.csproj | 1 + .../Launch/ServerConfiguration.cs | 4 +--- .../{ => Azure}/IAzureBlobStorageOptions.cs | 2 +- .../IAzureClientSecretCredentialOptions.cs | 6 ++++++ .../Options/Azure/IAzureCredentialOptions.cs | 8 ++++++++ .../{ => Azure}/IAzureTableStorageOptions.cs | 2 +- .../Options/AzureCredentialsOptions.cs | 16 +++++++++++++++ .../Options/ContentOptions.cs | 1 + .../Options/PartySearchTableOptions.cs | 3 +-- .../Options/StorageAccountOptions.cs | 10 ++-------- .../Azure/NamedBlobContainerClient.cs | 2 +- 18 files changed, 99 insertions(+), 31 deletions(-) rename GuildWarsPartySearch/Options/{ => Azure}/IAzureBlobStorageOptions.cs (60%) create mode 100644 GuildWarsPartySearch/Options/Azure/IAzureClientSecretCredentialOptions.cs create mode 100644 GuildWarsPartySearch/Options/Azure/IAzureCredentialOptions.cs rename GuildWarsPartySearch/Options/{ => Azure}/IAzureTableStorageOptions.cs (59%) create mode 100644 GuildWarsPartySearch/Options/AzureCredentialsOptions.cs diff --git a/.github/workflows/content-deploy.yaml b/.github/workflows/content-deploy.yaml index b9404df..27647c5 100644 --- a/.github/workflows/content-deploy.yaml +++ b/.github/workflows/content-deploy.yaml @@ -18,5 +18,5 @@ jobs: - name: Build and push content run: | cd GuildWarsPartySearch.FrontEnd - .\BuildAndUploadContent.ps1 -ConnectionString "${{ secrets.AZURE_TABLESTORAGE_CONNECTIONSTRING }}" -ContainerName "content" -SourceFolderPath Content + .\BuildAndUploadContent.ps1 -ContainerName content -SourceFolderPath Content -ClientId "${{ secrets.AZURE_CLIENT_ID }}" -ClientSecret "${{ secrets.AZURE_CLIENT_SECRET }}" -TenantId "${{ secrets.AZURE_TENANT_ID }}" -StorageAccountName "guildwarspartysearch" shell: pwsh \ No newline at end of file diff --git a/.github/workflows/docker-deploy.yaml b/.github/workflows/docker-deploy.yaml index 734bcfd..8ed03f7 100644 --- a/.github/workflows/docker-deploy.yaml +++ b/.github/workflows/docker-deploy.yaml @@ -31,9 +31,11 @@ jobs: cd GuildWarsPartySearch $content = Get-Content Config.Release.json $content = $content.Replace("[APIKEY]", "${{ secrets.APIKEY }}") - $content = $content.Replace("[AZURE_TABLESTORAGE_CONNECTIONSTRING]", "${{ secrets.AZURE_TABLESTORAGE_CONNECTIONSTRING }}") $content = $content.Replace("[CERTIFICATE_BASE64]", "${{ secrets.CERTIFICATE_BASE64 }}") $content = $content.Replace("[AZURE_INSIGHTS_INSTRUMENTATIONKEY]", "${{ secrets.AZURE_INSIGHTS_INSTRUMENTATIONKEY }}") + $content = $content.Replace("[AZURE_CLIENT_SECRET]", "${{ secrets.AZURE_CLIENT_SECRET }}") + $content = $content.Replace("[AZURE_CLIENT_ID]", "${{ secrets.AZURE_CLIENT_ID }}") + $content = $content.Replace("[AZURE_TENANT_ID]", "${{ secrets.AZURE_TENANT_ID }}") Set-Content -Path Config.Release.json -Value $content Write-Host "Placeholder replaced successfully in Config.Release.json" @@ -55,7 +57,7 @@ jobs: - name: Login to Azure shell: pwsh run: | - az login --service-principal -u "${{ secrets.AZURE_CLIENTID }}" -p "${{ secrets.AZURE_SECRET }}" --tenant "${{ secrets.AZURE_TENANT }}" + az login --service-principal -u "${{ secrets.AZURE_CLIENT_ID }}" -p "${{ secrets.AZURE_CLIENT_SECRET }}" --tenant "${{ secrets.AZURE_TENANT_ID }}" - name: Check and Delete Existing Container Instance shell: pwsh diff --git a/GuildWarsPartySearch.FrontEnd/BuildAndUploadContent.ps1 b/GuildWarsPartySearch.FrontEnd/BuildAndUploadContent.ps1 index 2d15473..51180df 100644 --- a/GuildWarsPartySearch.FrontEnd/BuildAndUploadContent.ps1 +++ b/GuildWarsPartySearch.FrontEnd/BuildAndUploadContent.ps1 @@ -1,6 +1,15 @@ param( [Parameter(Mandatory=$true)] - [string]$ConnectionString, + [string]$ClientId, + + [Parameter(Mandatory=$true)] + [string]$ClientSecret, + + [Parameter(Mandatory=$true)] + [string]$TenantId, + + [Parameter(Mandatory=$true)] + [string]$StorageAccountName, [Parameter(Mandatory=$true)] [string]$ContainerName, @@ -9,6 +18,6 @@ param( [string]$SourceFolderPath ) - -az storage blob delete-batch --source $ContainerName --connection-string $ConnectionString --pattern '*' -az storage blob upload-batch --destination $ContainerName --source $SourceFolderPath --connection-string $ConnectionString \ No newline at end of file +az login --service-principal -u $ClientId -p $ClientSecret --tenant $TenantId +az storage blob delete-batch --source $ContainerName --pattern '*' --account-name $StorageAccountName +az storage blob upload-batch --destination $ContainerName --source $SourceFolderPath --account-name $StorageAccountName \ No newline at end of file diff --git a/GuildWarsPartySearch/Config.Debug.json b/GuildWarsPartySearch/Config.Debug.json index 757a9c2..61bc782 100644 --- a/GuildWarsPartySearch/Config.Debug.json +++ b/GuildWarsPartySearch/Config.Debug.json @@ -42,7 +42,7 @@ "HeartbeatFrequency": "0:0:1" }, "StorageAccountOptions": { - "ConnectionString": "[AZURE_TABLESTORAGE_CONNECTIONSTRING]" + "Name": "guildwarspartysearch" }, "PartySearchTableOptions": { "TableName": "searches" @@ -62,5 +62,10 @@ 404, 429 ] + }, + "AzureCredentialsOptions": { + "ClientSecret": "[AZURE_CLIENT_SECRET]", + "ClientId": "[AZURE_CLIENT_ID]", + "TenantId": "[AZURE_TENANT_ID]" } } \ No newline at end of file diff --git a/GuildWarsPartySearch/Config.Release.json b/GuildWarsPartySearch/Config.Release.json index 796cb9a..5b384a3 100644 --- a/GuildWarsPartySearch/Config.Release.json +++ b/GuildWarsPartySearch/Config.Release.json @@ -42,7 +42,7 @@ "HeartbeatFrequency": "0:0:1" }, "StorageAccountOptions": { - "ConnectionString": "[AZURE_TABLESTORAGE_CONNECTIONSTRING]" + "Name": "guildwarspartysearch" }, "PartySearchTableOptions": { "TableName": "searches" @@ -62,5 +62,10 @@ 404, 429 ] + }, + "AzureCredentialsOptions": { + "ClientSecret": "[AZURE_CLIENT_SECRET]", + "ClientId": "[AZURE_CLIENT_ID]", + "TenantId": "[AZURE_TENANT_ID]" } } \ No newline at end of file diff --git a/GuildWarsPartySearch/Extensions/ServiceCollectionExtensions.cs b/GuildWarsPartySearch/Extensions/ServiceCollectionExtensions.cs index 63f06e8..bdb4251 100644 --- a/GuildWarsPartySearch/Extensions/ServiceCollectionExtensions.cs +++ b/GuildWarsPartySearch/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ -using GuildWarsPartySearch.Server.Options; +using Azure.Core; +using GuildWarsPartySearch.Server.Options; +using GuildWarsPartySearch.Server.Options.Azure; using GuildWarsPartySearch.Server.Services.Azure; using Microsoft.Extensions.Options; using System.Core.Extensions; @@ -13,10 +15,11 @@ public static IServiceCollection AddSingletonTableClient(this IService services.ThrowIfNull() .AddSingleton(sp => { + var tokenCredential = sp.GetRequiredService(); var storageOptions = sp.GetRequiredService>(); var clientOptions = sp.GetRequiredService>(); var logger = sp.GetRequiredService>>(); - return new NamedTableClient(logger, storageOptions.Value.ConnectionString!, clientOptions.Value.TableName); + return new NamedTableClient(logger, new Uri($"https://{storageOptions.Value.Name}.table.core.windows.net"), clientOptions.Value.TableName, tokenCredential, default); }); return services; @@ -28,10 +31,11 @@ public static IServiceCollection AddScopedTableClient(this IServiceCol services.ThrowIfNull() .AddScoped(sp => { + var tokenCredential = sp.GetRequiredService(); var storageOptions = sp.GetRequiredService>(); var clientOptions = sp.GetRequiredService>(); var logger = sp.GetRequiredService>>(); - return new NamedTableClient(logger, storageOptions.Value.ConnectionString!, clientOptions.Value.TableName); + return new NamedTableClient(logger, new Uri($"https://{storageOptions.Value.Name}.table.core.windows.net"), clientOptions.Value.TableName, tokenCredential, default); }); return services; @@ -43,9 +47,10 @@ public static IServiceCollection AddSingletonBlobContainerClient(this services.ThrowIfNull() .AddSingleton(sp => { + var tokenCredential = sp.GetRequiredService(); var storageOptions = sp.GetRequiredService>(); var clientOptions = sp.GetRequiredService>(); - return new NamedBlobContainerClient(storageOptions.Value.ConnectionString!, clientOptions.Value.ContainerName); + return new NamedBlobContainerClient(new Uri($"https://{storageOptions.Value.Name}.blob.core.windows.net/{clientOptions.Value.ContainerName}"), tokenCredential, default); }); return services; @@ -57,9 +62,10 @@ public static IServiceCollection AddScopedBlobContainerClient(this ISe services.ThrowIfNull() .AddScoped(sp => { + var tokenCredential = sp.GetRequiredService(); var storageOptions = sp.GetRequiredService>(); var clientOptions = sp.GetRequiredService>(); - return new NamedBlobContainerClient(storageOptions.Value.ConnectionString!, clientOptions.Value.ContainerName); + return new NamedBlobContainerClient(new Uri($"https://{storageOptions.Value.Name}.blob.core.windows.net/{clientOptions.Value.ContainerName}"), tokenCredential, default); }); return services; diff --git a/GuildWarsPartySearch/Extensions/WebApplicationBuilderExtensions.cs b/GuildWarsPartySearch/Extensions/WebApplicationBuilderExtensions.cs index bf96684..5ecd4de 100644 --- a/GuildWarsPartySearch/Extensions/WebApplicationBuilderExtensions.cs +++ b/GuildWarsPartySearch/Extensions/WebApplicationBuilderExtensions.cs @@ -1,4 +1,8 @@ -using GuildWarsPartySearch.Server.Attributes; +using Azure.Core; +using Azure.Identity; +using GuildWarsPartySearch.Server.Attributes; +using GuildWarsPartySearch.Server.Options.Azure; +using Microsoft.Extensions.Options; using System.Core.Extensions; using System.Extensions; @@ -6,6 +10,20 @@ namespace GuildWarsPartySearch.Server.Extensions; public static class WebApplicationBuilderExtensions { + public static WebApplicationBuilder ConfigureAzureClientSecretCredentials(this WebApplicationBuilder builder) + where TOptions : class, IAzureClientSecretCredentialOptions, new() + { + builder.ThrowIfNull() + .ConfigureExtended() + .Services.AddSingleton(sp => + { + var options = sp.GetRequiredService>().Value; + return new ClientSecretCredential(options.TenantId, options.ClientId, options.ClientSecret); + }); + + return builder; + } + public static WebApplicationBuilder ConfigureExtended(this WebApplicationBuilder builder) where TOptions : class, new() { diff --git a/GuildWarsPartySearch/GuildWarsPartySearch.Server.csproj b/GuildWarsPartySearch/GuildWarsPartySearch.Server.csproj index 02a83a2..e33ce9d 100644 --- a/GuildWarsPartySearch/GuildWarsPartySearch.Server.csproj +++ b/GuildWarsPartySearch/GuildWarsPartySearch.Server.csproj @@ -11,6 +11,7 @@ + diff --git a/GuildWarsPartySearch/Launch/ServerConfiguration.cs b/GuildWarsPartySearch/Launch/ServerConfiguration.cs index 650b618..a8161d6 100644 --- a/GuildWarsPartySearch/Launch/ServerConfiguration.cs +++ b/GuildWarsPartySearch/Launch/ServerConfiguration.cs @@ -1,5 +1,4 @@ using AspNetCoreRateLimit; -using Azure.Data.Tables; using GuildWarsPartySearch.Common.Converters; using GuildWarsPartySearch.Server.Endpoints; using GuildWarsPartySearch.Server.Extensions; @@ -13,8 +12,6 @@ using GuildWarsPartySearch.Server.Services.PartySearch; using GuildWarsPartySearch.Server.Telemetry; using Microsoft.ApplicationInsights.Extensibility; -using Microsoft.Extensions.Azure; -using Microsoft.Extensions.Options; using System.Core.Extensions; using System.Extensions; using System.Text.Json.Serialization; @@ -65,6 +62,7 @@ public static ILoggingBuilder SetupLogging(this ILoggingBuilder builder) public static WebApplicationBuilder SetupOptions(this WebApplicationBuilder builder) { return builder.ThrowIfNull() + .ConfigureAzureClientSecretCredentials() .ConfigureExtended() .ConfigureExtended() .ConfigureExtended() diff --git a/GuildWarsPartySearch/Options/IAzureBlobStorageOptions.cs b/GuildWarsPartySearch/Options/Azure/IAzureBlobStorageOptions.cs similarity index 60% rename from GuildWarsPartySearch/Options/IAzureBlobStorageOptions.cs rename to GuildWarsPartySearch/Options/Azure/IAzureBlobStorageOptions.cs index 6855a56..ca58bc4 100644 --- a/GuildWarsPartySearch/Options/IAzureBlobStorageOptions.cs +++ b/GuildWarsPartySearch/Options/Azure/IAzureBlobStorageOptions.cs @@ -1,4 +1,4 @@ -namespace GuildWarsPartySearch.Server.Options; +namespace GuildWarsPartySearch.Server.Options.Azure; public interface IAzureBlobStorageOptions { diff --git a/GuildWarsPartySearch/Options/Azure/IAzureClientSecretCredentialOptions.cs b/GuildWarsPartySearch/Options/Azure/IAzureClientSecretCredentialOptions.cs new file mode 100644 index 0000000..255e1a7 --- /dev/null +++ b/GuildWarsPartySearch/Options/Azure/IAzureClientSecretCredentialOptions.cs @@ -0,0 +1,6 @@ +namespace GuildWarsPartySearch.Server.Options.Azure; + +public interface IAzureClientSecretCredentialOptions : IAzureCredentialOptions +{ + string ClientSecret { get; set; } +} diff --git a/GuildWarsPartySearch/Options/Azure/IAzureCredentialOptions.cs b/GuildWarsPartySearch/Options/Azure/IAzureCredentialOptions.cs new file mode 100644 index 0000000..2e620c3 --- /dev/null +++ b/GuildWarsPartySearch/Options/Azure/IAzureCredentialOptions.cs @@ -0,0 +1,8 @@ +namespace GuildWarsPartySearch.Server.Options.Azure; + +public interface IAzureCredentialOptions +{ + string ClientId { get; set; } + + string TenantId { get; set; } +} diff --git a/GuildWarsPartySearch/Options/IAzureTableStorageOptions.cs b/GuildWarsPartySearch/Options/Azure/IAzureTableStorageOptions.cs similarity index 59% rename from GuildWarsPartySearch/Options/IAzureTableStorageOptions.cs rename to GuildWarsPartySearch/Options/Azure/IAzureTableStorageOptions.cs index 66c6e4e..da4d7c1 100644 --- a/GuildWarsPartySearch/Options/IAzureTableStorageOptions.cs +++ b/GuildWarsPartySearch/Options/Azure/IAzureTableStorageOptions.cs @@ -1,4 +1,4 @@ -namespace GuildWarsPartySearch.Server.Options; +namespace GuildWarsPartySearch.Server.Options.Azure; public interface IAzureTableStorageOptions { diff --git a/GuildWarsPartySearch/Options/AzureCredentialsOptions.cs b/GuildWarsPartySearch/Options/AzureCredentialsOptions.cs new file mode 100644 index 0000000..b3b485c --- /dev/null +++ b/GuildWarsPartySearch/Options/AzureCredentialsOptions.cs @@ -0,0 +1,16 @@ +using GuildWarsPartySearch.Server.Options.Azure; +using System.Text.Json.Serialization; + +namespace GuildWarsPartySearch.Server.Options; + +public sealed class AzureCredentialsOptions : IAzureClientSecretCredentialOptions +{ + [JsonPropertyName(nameof(ClientSecret))] + public string ClientSecret { get; set; } = default!; + + [JsonPropertyName(nameof(ClientId))] + public string ClientId { get; set; } = default!; + + [JsonPropertyName(nameof(TenantId))] + public string TenantId { get; set; } = default!; +} diff --git a/GuildWarsPartySearch/Options/ContentOptions.cs b/GuildWarsPartySearch/Options/ContentOptions.cs index b0755a3..ebb1d8c 100644 --- a/GuildWarsPartySearch/Options/ContentOptions.cs +++ b/GuildWarsPartySearch/Options/ContentOptions.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using GuildWarsPartySearch.Server.Options.Azure; namespace GuildWarsPartySearch.Server.Options; diff --git a/GuildWarsPartySearch/Options/PartySearchTableOptions.cs b/GuildWarsPartySearch/Options/PartySearchTableOptions.cs index 0dfcacd..9b85336 100644 --- a/GuildWarsPartySearch/Options/PartySearchTableOptions.cs +++ b/GuildWarsPartySearch/Options/PartySearchTableOptions.cs @@ -1,9 +1,8 @@ -using System.Text.Json.Serialization; +using GuildWarsPartySearch.Server.Options.Azure; namespace GuildWarsPartySearch.Server.Options; public class PartySearchTableOptions : IAzureTableStorageOptions { - [JsonPropertyName(nameof(TableName))] public string TableName { get; set; } = default!; } diff --git a/GuildWarsPartySearch/Options/StorageAccountOptions.cs b/GuildWarsPartySearch/Options/StorageAccountOptions.cs index 6724955..2ccd97d 100644 --- a/GuildWarsPartySearch/Options/StorageAccountOptions.cs +++ b/GuildWarsPartySearch/Options/StorageAccountOptions.cs @@ -4,12 +4,6 @@ namespace GuildWarsPartySearch.Server.Options; public sealed class StorageAccountOptions { - [JsonPropertyName(nameof(TableName))] - public string? TableName { get; set; } - - [JsonPropertyName(nameof(ConnectionString))] - public string? ConnectionString { get; set; } - - [JsonPropertyName(nameof(ContainerName))] - public string? ContainerName { get; set; } + [JsonPropertyName(nameof(Name))] + public string? Name { get; set; } } diff --git a/GuildWarsPartySearch/Services/Azure/NamedBlobContainerClient.cs b/GuildWarsPartySearch/Services/Azure/NamedBlobContainerClient.cs index 69d1c52..6170f03 100644 --- a/GuildWarsPartySearch/Services/Azure/NamedBlobContainerClient.cs +++ b/GuildWarsPartySearch/Services/Azure/NamedBlobContainerClient.cs @@ -3,7 +3,7 @@ using Azure.Storage; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; -using GuildWarsPartySearch.Server.Options; +using GuildWarsPartySearch.Server.Options.Azure; namespace GuildWarsPartySearch.Server.Services.Azure;