From daf2e0321f4f9b00ae8f0fb32eeda553a123ed56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Nunes?= Date: Sun, 14 Apr 2024 11:43:44 +0100 Subject: [PATCH] feat: Support azure blob storage auth with managed identity. --- .../MediaBlobContainerTenantEvents.cs | 5 ++-- .../MediaBlobStorageOptionsConfiguration.cs | 4 +++ .../BlobAuthenticationType.cs | 7 +++++ .../BlobContainerClientFactory.cs | 29 +++++++++++++++++++ .../BlobFileStore.cs | 2 +- .../BlobStorageOptions.cs | 15 ++++++++++ .../OrchardCore.FileStorage.AzureBlob.csproj | 2 ++ 7 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobAuthenticationType.cs create mode 100644 src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobContainerClientFactory.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobContainerTenantEvents.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobContainerTenantEvents.cs index 8d9905048f4a..a592c59da9d9 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobContainerTenantEvents.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobContainerTenantEvents.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Options; using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Removing; +using OrchardCore.FileStorage.AzureBlob; using OrchardCore.Modules; namespace OrchardCore.Media.Azure @@ -47,7 +48,7 @@ public override async Task ActivatingAsync() try { - var _blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); + var _blobContainer = BlobContainerClientFactory.Create(_options); var response = await _blobContainer.CreateIfNotExistsAsync(PublicAccessType.None); _logger.LogDebug("Azure Media Storage container {ContainerName} created.", _options.ContainerName); @@ -70,7 +71,7 @@ public override async Task RemovingAsync(ShellRemovingContext context) try { - var _blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); + var _blobContainer = BlobContainerClientFactory.Create(_options); var response = await _blobContainer.DeleteIfExistsAsync(); if (!response.Value) diff --git a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsConfiguration.cs index 522b5b5db257..88e323675ac0 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media.Azure/MediaBlobStorageOptionsConfiguration.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Configuration; +using OrchardCore.FileStorage.AzureBlob; namespace OrchardCore.Media.Azure { @@ -37,6 +38,9 @@ public void Configure(MediaBlobStorageOptions options) options.ConnectionString = section.GetValue(nameof(options.ConnectionString), string.Empty); options.CreateContainer = section.GetValue(nameof(options.CreateContainer), true); options.RemoveContainer = section.GetValue(nameof(options.RemoveContainer), false); + options.IdentityClientId = section.GetValue(nameof(options.IdentityClientId), string.Empty); + options.AuthenticationType = section.GetValue(nameof(options.AuthenticationType), BlobAuthenticationType.ConnectionString); + options.StorageAccountName = section.GetValue(nameof(options.StorageAccountName), string.Empty); var templateOptions = new TemplateOptions(); var templateContext = new TemplateContext(templateOptions); diff --git a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobAuthenticationType.cs b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobAuthenticationType.cs new file mode 100644 index 000000000000..6eb15ca71732 --- /dev/null +++ b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobAuthenticationType.cs @@ -0,0 +1,7 @@ +namespace OrchardCore.FileStorage.AzureBlob; + +public enum BlobAuthenticationType +{ + ConnectionString = 0, + ManagedIdentity = 1, +} diff --git a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobContainerClientFactory.cs b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobContainerClientFactory.cs new file mode 100644 index 000000000000..6eca87782328 --- /dev/null +++ b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobContainerClientFactory.cs @@ -0,0 +1,29 @@ +using System; +using Azure.Identity; +using Azure.Storage.Blobs; + +namespace OrchardCore.FileStorage.AzureBlob; + +public class BlobContainerClientFactory +{ + public static BlobContainerClient Create(BlobStorageOptions options) + { + if (options.AuthenticationType == BlobAuthenticationType.ConnectionString) + { + return new BlobContainerClient(options.ConnectionString, options.ContainerName); + } + else if (options.AuthenticationType == BlobAuthenticationType.ManagedIdentity && !String.IsNullOrWhiteSpace(options.IdentityClientId)) + { + return new BlobContainerClient(GetStorageAccountUri(options.StorageAccountName), new ManagedIdentityCredential(options.IdentityClientId)); + } + else + { + return new BlobContainerClient(GetStorageAccountUri(options.StorageAccountName), new DefaultAzureCredential()); + } + } + + private static Uri GetStorageAccountUri(string storageAccountName) + { + return new Uri($"https://{storageAccountName}.blob.core.windows.net"); + } +} diff --git a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobFileStore.cs b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobFileStore.cs index c06ea3686d73..693f6c5e6aae 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobFileStore.cs +++ b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobFileStore.cs @@ -52,7 +52,7 @@ public BlobFileStore(BlobStorageOptions options, IClock clock, IContentTypeProvi _clock = clock; _contentTypeProvider = contentTypeProvider; - _blobContainer = new BlobContainerClient(_options.ConnectionString, _options.ContainerName); + _blobContainer = BlobContainerClientFactory.Create(_options); if (!string.IsNullOrEmpty(_options.BasePath)) { diff --git a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs index 24130b7f5122..2192734cb54f 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs +++ b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/BlobStorageOptions.cs @@ -16,5 +16,20 @@ public abstract class BlobStorageOptions /// The base directory path to use inside the container for this stores contents. /// public string BasePath { get; set; } + + /// + /// The Azure storage account name. Required for Identity authentication type. + /// + public string StorageAccountName { get; set; } + + /// + /// The authentication type. + /// + public BlobAuthenticationType AuthenticationType { get; set; } + + /// + /// The Identity Client Id. Required for ManagedIdentity authentication type. + /// + public string IdentityClientId { get; set; } } } diff --git a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/OrchardCore.FileStorage.AzureBlob.csproj b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/OrchardCore.FileStorage.AzureBlob.csproj index 8b0f41f06151..0b1d1b3925d0 100644 --- a/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/OrchardCore.FileStorage.AzureBlob.csproj +++ b/src/OrchardCore/OrchardCore.FileStorage.AzureBlob/OrchardCore.FileStorage.AzureBlob.csproj @@ -16,6 +16,8 @@ + +