From b730d4c86d957ce1512411f5d4e98d7a90889348 Mon Sep 17 00:00:00 2001 From: Timothy Trowbridge Date: Thu, 17 Sep 2026 23:23:13 -0300 Subject: [PATCH 1/3] Demo: resource-first configuration with an optional Blob Storage test file - appsettings.json uses the Csag.WorkloadIdentity resource shape: AzureSql over the Google provider with empty placeholders, BlobStorage as a commented example, and a BlobStorageTestFile section (Endpoint, FilePath). - The BlobServiceClient is built on the library's WorkloadIdentityTokenCredential over IBlobStorageTokenProvider; BlobStorageService downloads /. Both are registered only when the BlobStorage resource and the test file are configured, so /test returns the SQL rows with blobSkipped=true otherwise and the placeholder container smoke test still works. - Azure.Storage.Blobs 12.29.2 added for the Demo through central package management; the Demo lock file is updated. - The CI docker smoke test passes Csag.WorkloadIdentity__AzureSql__Provider=Google. - The Demo README covers Google on Cloud Run, the managed identity variant for Azure hosts, the exact configuration keys, the optional blob test and the docker commands. Co-Authored-By: Claude Fable 5.1 --- .changeset/workload-identity-demo.md | 4 + .github/workflows/ci.yml | 2 + .../Csag.WorkloadIdentity.Demo.csproj | 1 + .../Extensions/BlobStorageExtensions.cs | 34 ++++++++ Csag.WorkloadIdentity.Demo/Program.cs | 17 ++-- Csag.WorkloadIdentity.Demo/README.md | 86 +++++++++++++++---- .../Services/BlobStorageService.cs | 28 ++++++ .../Services/IBlobStorageService.cs | 7 ++ Csag.WorkloadIdentity.Demo/appsettings.json | 13 +++ Csag.WorkloadIdentity.Demo/packages.lock.json | 24 ++++++ Directory.Packages.props | 1 + README.md | 2 +- 12 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 .changeset/workload-identity-demo.md create mode 100644 Csag.WorkloadIdentity.Demo/Extensions/BlobStorageExtensions.cs create mode 100644 Csag.WorkloadIdentity.Demo/Services/BlobStorageService.cs create mode 100644 Csag.WorkloadIdentity.Demo/Services/IBlobStorageService.cs diff --git a/.changeset/workload-identity-demo.md b/.changeset/workload-identity-demo.md new file mode 100644 index 0000000..0b74d0f --- /dev/null +++ b/.changeset/workload-identity-demo.md @@ -0,0 +1,4 @@ +--- +--- + +Bring the Demo to the resource-first configuration: Azure SQL over Google federation on Cloud Run as the main path, a managed identity variant for Azure hosts, and an optional Blob Storage test file downloaded through `WorkloadIdentityTokenCredential`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index accc31c..a4e4fe6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,9 +91,11 @@ jobs: run: docker build -f Csag.WorkloadIdentity.Demo/Dockerfile -t csag-demo . # The library validates its settings at startup, so the container only reaches "/" with placeholder values. + # Blob Storage is left unconfigured on purpose: the Demo then skips it instead of needing a storage account. - name: Smoke test Demo image run: | docker run -d -p 8080:8080 --name csag-demo \ + -e Csag.WorkloadIdentity__AzureSql__Provider=Google \ -e Csag.WorkloadIdentity__AzureSql__Google__TenantId=placeholder \ -e Csag.WorkloadIdentity__AzureSql__Google__ClientId=placeholder \ -e Csag.WorkloadIdentity__AzureSql__Google__ServiceAccountEmail=placeholder@example.invalid \ diff --git a/Csag.WorkloadIdentity.Demo/Csag.WorkloadIdentity.Demo.csproj b/Csag.WorkloadIdentity.Demo/Csag.WorkloadIdentity.Demo.csproj index 423a0b7..b561fa3 100644 --- a/Csag.WorkloadIdentity.Demo/Csag.WorkloadIdentity.Demo.csproj +++ b/Csag.WorkloadIdentity.Demo/Csag.WorkloadIdentity.Demo.csproj @@ -7,6 +7,7 @@ + diff --git a/Csag.WorkloadIdentity.Demo/Extensions/BlobStorageExtensions.cs b/Csag.WorkloadIdentity.Demo/Extensions/BlobStorageExtensions.cs new file mode 100644 index 0000000..57ae501 --- /dev/null +++ b/Csag.WorkloadIdentity.Demo/Extensions/BlobStorageExtensions.cs @@ -0,0 +1,34 @@ +namespace Csag.WorkloadIdentity.Demo.Extensions +{ + using System; + using Azure.Storage.Blobs; + using Csag.WorkloadIdentity.Abstractions; + using Csag.WorkloadIdentity.Demo.Services; + using Csag.WorkloadIdentity.Options; + + public static class BlobStorageExtensions + { + public static IServiceCollection AddBlobStorageClient(this IServiceCollection services, IConfiguration configuration) + { + // Blob Storage is optional in the Demo. The client is only registered when the library has a BlobStorage + // resource to obtain tokens for and a test file is named, so that a run configured for Azure SQL alone + // still starts and /test reports the blob part as skipped. + var resourceSection = configuration.GetSection($"{WorkloadIdentityOptions.ConfigurationSectionName}:{nameof(WorkloadIdentityOptions.BlobStorage)}"); + var endpoint = configuration["BlobStorageTestFile:Endpoint"]; + var filePath = configuration["BlobStorageTestFile:FilePath"]; + if (!resourceSection.Exists() || string.IsNullOrWhiteSpace(endpoint) || string.IsNullOrWhiteSpace(filePath)) + { + return services; + } + + // The library's TokenCredential adapter serves the client from the held Blob Storage token and reports + // that token's real expiry, so the Azure SDK asks for a new one only when the provider refreshes it. + services.AddSingleton(serviceProvider => new BlobServiceClient( + new Uri(endpoint), + new WorkloadIdentityTokenCredential(serviceProvider.GetRequiredService()))); + services.AddSingleton(serviceProvider => new BlobStorageService(serviceProvider.GetRequiredService(), filePath)); + + return services; + } + } +} diff --git a/Csag.WorkloadIdentity.Demo/Program.cs b/Csag.WorkloadIdentity.Demo/Program.cs index 86fd17e..0e92d8b 100644 --- a/Csag.WorkloadIdentity.Demo/Program.cs +++ b/Csag.WorkloadIdentity.Demo/Program.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Mvc; using Csag.WorkloadIdentity; using Csag.WorkloadIdentity.Demo.Database; +using Csag.WorkloadIdentity.Demo.Extensions; +using Csag.WorkloadIdentity.Demo.Services; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -8,14 +10,15 @@ builder.Services.AddScoped(); builder.Services.AddWorkloadIdentity(builder.Configuration); +builder.Services.AddBlobStorageClient(builder.Configuration); var app = builder.Build(); app.UseStaticFiles(); -app.MapGet("/", () => Results.Ok("Cloud Run to Azure SQL via Workload Identity Federation.")); +app.MapGet("/", () => Results.Ok("Azure SQL and Blob Storage through Csag.WorkloadIdentity, without stored credentials.")); -app.MapGet("/test", async ([FromServices] IAppDbContextFactory dbFactory, [FromServices] ILogger logger, CancellationToken cancellationToken) => +app.MapGet("/test", async ([FromServices] IAppDbContextFactory dbFactory, [FromServices] IBlobStorageService? blobService, [FromServices] ILogger logger, CancellationToken cancellationToken) => { try { @@ -28,7 +31,11 @@ } var rows = await context.TestTable.OrderBy(e => e.Id).ToListAsync(cancellationToken); - return Results.Ok(new { count, rows, }); + + // The Blob Storage service exists only when the BlobStorage resource and a test file are configured. + var blobContent = blobService is null ? null : await blobService.DownloadTestFileAsync(cancellationToken); + + return Results.Ok(new { count, rows, blobContent, blobSkipped = blobService is null, }); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { @@ -37,8 +44,8 @@ } catch (Exception ex) { - // The endpoint is unauthenticated, so token-exchange and SQL failures go to the log, not the response. - logger.LogError(ex, "Querying TestTable failed."); + // The endpoint is unauthenticated, so token, SQL and Blob Storage failures go to the log, not the response. + logger.LogError(ex, "Reading TestTable or downloading the test blob failed."); return Results.Problem(); } }); diff --git a/Csag.WorkloadIdentity.Demo/README.md b/Csag.WorkloadIdentity.Demo/README.md index e2a4fa8..03f2107 100644 --- a/Csag.WorkloadIdentity.Demo/README.md +++ b/Csag.WorkloadIdentity.Demo/README.md @@ -1,42 +1,82 @@ # Csag.WorkloadIdentity.Demo -A minimal ASP.NET Core app that runs on Google Cloud Run and reads from Azure SQL without a database password. The `Csag.WorkloadIdentity` library turns the app's Google identity into an Azure AD access token, and [`Database/AppDbContextFactory.cs`](Database/AppDbContextFactory.cs) attaches that token to each `SqlConnection`. +A minimal ASP.NET Core app that reads a table from Azure SQL and, optionally, a file from Azure Blob Storage without a stored credential. The `Csag.WorkloadIdentity` library obtains the access tokens with the app's workload identity: on Google Cloud Run that is the app's Google service account, exchanged for a Microsoft Entra ID token through workload identity federation; on an Azure host it is the resource's managed identity. [`Database/AppDbContextFactory.cs`](Database/AppDbContextFactory.cs) attaches the Azure SQL token to each `SqlConnection`, and [`Extensions/BlobStorageExtensions.cs`](Extensions/BlobStorageExtensions.cs) builds the `BlobServiceClient` on the library's `WorkloadIdentityTokenCredential`. | Route | Behaviour | |---|---| | `GET /` | Greeting; proves the container is up. | -| `GET /test` | Counts and lists the rows of `dbo.TestTable`: `200` with `{ "count": n, "rows": [...] }`, `404` if the table is empty, `500` (generic problem response) if the token exchange or the SQL connection fails. The reason is in the application log. | +| `GET /test` | Counts and lists the rows of `dbo.TestTable`, then downloads the test blob: `200` with `{ "count": n, "rows": [...], "blobContent": "...", "blobSkipped": false }`, `404` if the table is empty, `500` (generic problem response) if a token request, the SQL connection or the download fails. The reason is in the application log. Without the optional Blob Storage settings the response carries `"blobContent": null, "blobSkipped": true`. | ## Prerequisites -1. The cloud side described in [docs/cloud-identity-setup.md](../docs/cloud-identity-setup.md): a Google service account, an Azure AD app registration with a federated credential for it, and a database user for that app registration. +The main path is Google Cloud Run with the `Google` provider: + +1. The cloud side described in [docs/cloud-identity-setup.md](../docs/cloud-identity-setup.md): a Google service account, a Microsoft Entra app registration with a federated credential for it, and a database user for that app registration. 2. Google Application Default Credentials (ADC), which the library uses to ask Google for an ID token for the service account: - **On Cloud Run** there is nothing to configure: set the service's runtime service account to the configured service account (it needs `roles/iam.serviceAccountOpenIdTokenCreator` on itself). - **On a workstation** run `gcloud auth application-default login`. Your Google account needs `roles/iam.serviceAccountOpenIdTokenCreator` on the service account (granted on the service account, not on the project), and the IAM Service Account Credentials API must be enabled in the project (`gcloud services enable iamcredentials.googleapis.com`). 3. The .NET SDK pinned in [global.json](../global.json), and Docker if you want to run the container. +An app that runs on Azure needs none of the Google side; see [Managed identity on an Azure host](#managed-identity-on-an-azure-host). + ## Settings -`appsettings.json` ships with the four values empty. Provide them through user secrets on a workstation or environment variables in a container: +The library binds the `Csag.WorkloadIdentity` section, which has one sub-section per resource. `appsettings.json` selects the `Google` provider for `AzureSql` and ships with the values empty. Provide them through user secrets on a workstation or environment variables in a container (`__` stands for the `:` separator; the `.` in the section name is part of the variable name): | Setting | User-secrets / JSON key | Environment variable | |---|---|---| -| Azure AD tenant ID | `Csag.WorkloadIdentity:TenantId` | `Csag.WorkloadIdentity__TenantId` | -| Azure AD application (client) ID | `Csag.WorkloadIdentity:ClientId` | `Csag.WorkloadIdentity__ClientId` | -| Google service account email | `Csag.WorkloadIdentity:Google:ServiceAccountEmail` | `Csag.WorkloadIdentity__Google__ServiceAccountEmail` | +| Identity provider for Azure SQL (`Google`, set in `appsettings.json`) | `Csag.WorkloadIdentity:AzureSql:Provider` | `Csag.WorkloadIdentity__AzureSql__Provider` | +| Microsoft Entra tenant ID | `Csag.WorkloadIdentity:AzureSql:Google:TenantId` | `Csag.WorkloadIdentity__AzureSql__Google__TenantId` | +| Application (client) ID of the app registration | `Csag.WorkloadIdentity:AzureSql:Google:ClientId` | `Csag.WorkloadIdentity__AzureSql__Google__ClientId` | +| Google service account email | `Csag.WorkloadIdentity:AzureSql:Google:ServiceAccountEmail` | `Csag.WorkloadIdentity__AzureSql__Google__ServiceAccountEmail` | | Azure SQL connection string | `ConnectionStrings:DefaultConnection` | `ConnectionStrings__DefaultConnection` | -The first three are validated at startup, and the app refuses to start if any of them is missing. The connection string is checked on the first request to `/test`. +The three Google values are validated at startup, and the app refuses to start if any of them is missing. The connection string is checked on the first request to `/test`. ```shell -dotnet user-secrets set "Csag.WorkloadIdentity:TenantId" "" --project Csag.WorkloadIdentity.Demo -dotnet user-secrets set "Csag.WorkloadIdentity:ClientId" "" --project Csag.WorkloadIdentity.Demo -dotnet user-secrets set "Csag.WorkloadIdentity:Google:ServiceAccountEmail" "@.iam.gserviceaccount.com" --project Csag.WorkloadIdentity.Demo +dotnet user-secrets set "Csag.WorkloadIdentity:AzureSql:Google:TenantId" "" --project Csag.WorkloadIdentity.Demo +dotnet user-secrets set "Csag.WorkloadIdentity:AzureSql:Google:ClientId" "" --project Csag.WorkloadIdentity.Demo +dotnet user-secrets set "Csag.WorkloadIdentity:AzureSql:Google:ServiceAccountEmail" "@.iam.gserviceaccount.com" --project Csag.WorkloadIdentity.Demo dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=tcp:.database.windows.net,1433;Initial Catalog=;Encrypt=True" --project Csag.WorkloadIdentity.Demo ``` The connection string must not contain `User ID`, `Password`, `Integrated Security` or `Authentication`: the access token is the credential, and `SqlClient` rejects a connection string that also carries one of those. +### Optional: Blob Storage test file + +To make `/test` also download a blob, configure a `BlobStorage` resource (the commented block in `appsettings.json` shows the shape) and name the file: + +| Setting | User-secrets / JSON key | Environment variable | +|---|---|---| +| Identity provider for Blob Storage | `Csag.WorkloadIdentity:BlobStorage:Provider` | `Csag.WorkloadIdentity__BlobStorage__Provider` | +| Tenant ID, client ID and service account email, as for Azure SQL | `Csag.WorkloadIdentity:BlobStorage:Google:TenantId`, `...:ClientId`, `...:ServiceAccountEmail` | `Csag.WorkloadIdentity__BlobStorage__Google__TenantId`, `...__ClientId`, `...__ServiceAccountEmail` | +| Blob service endpoint of the storage account | `BlobStorageTestFile:Endpoint` | `BlobStorageTestFile__Endpoint` | +| Test file as `/` | `BlobStorageTestFile:FilePath` | `BlobStorageTestFile__FilePath` | + +The same app registration can serve both resources: repeat the three Google values under `BlobStorage` and assign its service principal the **Storage Blob Data Reader** role on the container or the storage account. The endpoint is `https://.blob.core.windows.net`. + +```shell +dotnet user-secrets set "Csag.WorkloadIdentity:BlobStorage:Provider" "Google" --project Csag.WorkloadIdentity.Demo +dotnet user-secrets set "Csag.WorkloadIdentity:BlobStorage:Google:TenantId" "" --project Csag.WorkloadIdentity.Demo +dotnet user-secrets set "Csag.WorkloadIdentity:BlobStorage:Google:ClientId" "" --project Csag.WorkloadIdentity.Demo +dotnet user-secrets set "Csag.WorkloadIdentity:BlobStorage:Google:ServiceAccountEmail" "@.iam.gserviceaccount.com" --project Csag.WorkloadIdentity.Demo +dotnet user-secrets set "BlobStorageTestFile:Endpoint" "https://.blob.core.windows.net" --project Csag.WorkloadIdentity.Demo +dotnet user-secrets set "BlobStorageTestFile:FilePath" "/" --project Csag.WorkloadIdentity.Demo +``` + +Blob Storage is optional: when the `BlobStorage` section or either `BlobStorageTestFile` value is missing, the app does not register the Blob Storage client and `/test` returns the SQL rows with `"blobSkipped": true`. A configured `BlobStorage` section is validated at startup like `AzureSql`. + +### Managed identity on an Azure host + +When the Demo runs on an Azure resource (App Service, Container Apps, AKS, a VM), it can obtain the tokens from that resource's managed identity instead; there is then no Google side and no ADC. Select the `ManagedIdentity` provider per resource, and either the system-assigned identity or a user-assigned one by client ID: + +```shell +Csag.WorkloadIdentity__AzureSql__Provider=ManagedIdentity +Csag.WorkloadIdentity__AzureSql__ManagedIdentity__UseSystemAssignedIdentity=true +``` + +or, for a user-assigned identity, `Csag.WorkloadIdentity__AzureSql__ManagedIdentity__ClientId=` instead of the second line. The `Google` values in `appsettings.json` are ignored for a resource whose provider is `ManagedIdentity`. Create the database user for the managed identity (`CREATE USER [] FROM EXTERNAL PROVIDER;` with the same roles as in the setup guide) and, for the optional blob test, configure `BlobStorage` the same way and assign the identity **Storage Blob Data Reader**. + ## Schema The app never creates schema. The identity it runs as only has `db_datareader` and `db_datawriter` (see the setup guide), so create the table once as the server's Microsoft Entra admin or another login with DDL rights: @@ -75,9 +115,10 @@ The container listens on port 8080 and runs as the non-root `app` user (uid 1654 ```shell docker run --rm -p 8080:8080 --user "$(id -u):$(id -g)" \ - -e Csag.WorkloadIdentity__TenantId="" \ - -e Csag.WorkloadIdentity__ClientId="" \ - -e Csag.WorkloadIdentity__Google__ServiceAccountEmail="@.iam.gserviceaccount.com" \ + -e Csag.WorkloadIdentity__AzureSql__Provider=Google \ + -e Csag.WorkloadIdentity__AzureSql__Google__TenantId="" \ + -e Csag.WorkloadIdentity__AzureSql__Google__ClientId="" \ + -e Csag.WorkloadIdentity__AzureSql__Google__ServiceAccountEmail="@.iam.gserviceaccount.com" \ -e ConnectionStrings__DefaultConnection="Server=tcp:.database.windows.net,1433;Initial Catalog=;Encrypt=True" \ -v "$HOME/.config/gcloud/application_default_credentials.json:/adc.json:ro" \ -e GOOGLE_APPLICATION_CREDENTIALS=/adc.json \ @@ -90,8 +131,21 @@ The container stays in the foreground as well; from a second terminal: curl http://localhost:8080/test ``` -On Windows the credential file is `%APPDATA%\gcloud\application_default_credentials.json`. +On Windows the credential file is `%APPDATA%\gcloud\application_default_credentials.json`. Add the `Csag.WorkloadIdentity__BlobStorage__*` and `BlobStorageTestFile__*` variables from the table above to include the blob test. + +To check only that the image starts, run it with placeholder values (the library validates their presence, not their meaning) and request `/`, as the CI workflow does: + +```shell +docker run -d -p 8080:8080 --name csag-demo \ + -e Csag.WorkloadIdentity__AzureSql__Provider=Google \ + -e Csag.WorkloadIdentity__AzureSql__Google__TenantId=placeholder \ + -e Csag.WorkloadIdentity__AzureSql__Google__ClientId=placeholder \ + -e Csag.WorkloadIdentity__AzureSql__Google__ServiceAccountEmail=placeholder@example.invalid \ + csag-demo +curl http://localhost:8080/ +docker stop csag-demo +``` ## Deploy to Cloud Run -Push the image to Artifact Registry and deploy it with the runtime service account set to the configured Google service account and the four settings supplied as environment variables. Cloud Run sends traffic to port 8080, which is the port the image listens on. The [setup guide](../docs/cloud-identity-setup.md) covers the Cloud Run configuration in detail. +Push the image to Artifact Registry and deploy it with the runtime service account set to the configured Google service account and the settings from the tables above supplied as environment variables (`Csag.WorkloadIdentity__AzureSql__Provider=Google`, the three `Csag.WorkloadIdentity__AzureSql__Google__*` values and `ConnectionStrings__DefaultConnection`, plus the Blob Storage variables if you want the blob test). Cloud Run sends traffic to port 8080, which is the port the image listens on. The [setup guide](../docs/cloud-identity-setup.md) covers the Cloud Run configuration in detail. diff --git a/Csag.WorkloadIdentity.Demo/Services/BlobStorageService.cs b/Csag.WorkloadIdentity.Demo/Services/BlobStorageService.cs new file mode 100644 index 0000000..8ab8208 --- /dev/null +++ b/Csag.WorkloadIdentity.Demo/Services/BlobStorageService.cs @@ -0,0 +1,28 @@ +namespace Csag.WorkloadIdentity.Demo.Services +{ + using System; + using Azure.Storage.Blobs; + + public class BlobStorageService : IBlobStorageService + { + private readonly BlobClient blobClient; + + public BlobStorageService(BlobServiceClient blobServiceClient, string filePath) + { + // The test file is addressed as "/"; the blob name may itself contain slashes. + var separator = filePath.IndexOf('/'); + if (separator <= 0 || separator == filePath.Length - 1) + { + throw new ArgumentException("The test file path must have the form '/'.", nameof(filePath)); + } + + this.blobClient = blobServiceClient.GetBlobContainerClient(filePath[..separator]).GetBlobClient(filePath[(separator + 1)..]); + } + + public async Task DownloadTestFileAsync(CancellationToken cancellationToken) + { + var download = await this.blobClient.DownloadContentAsync(cancellationToken); + return download.Value.Content.ToString(); + } + } +} diff --git a/Csag.WorkloadIdentity.Demo/Services/IBlobStorageService.cs b/Csag.WorkloadIdentity.Demo/Services/IBlobStorageService.cs new file mode 100644 index 0000000..414e6b9 --- /dev/null +++ b/Csag.WorkloadIdentity.Demo/Services/IBlobStorageService.cs @@ -0,0 +1,7 @@ +namespace Csag.WorkloadIdentity.Demo.Services +{ + public interface IBlobStorageService + { + Task DownloadTestFileAsync(CancellationToken cancellationToken); + } +} diff --git a/Csag.WorkloadIdentity.Demo/appsettings.json b/Csag.WorkloadIdentity.Demo/appsettings.json index a8b121f..6dd45c4 100644 --- a/Csag.WorkloadIdentity.Demo/appsettings.json +++ b/Csag.WorkloadIdentity.Demo/appsettings.json @@ -15,8 +15,21 @@ "ServiceAccountEmail": "" } } + // Optional. Together with "BlobStorageTestFile" below, this makes /test also download a blob. + // "BlobStorage": { + // "Provider": "Google", + // "Google": { + // "TenantId": "", + // "ClientId": "", + // "ServiceAccountEmail": "" + // } + // } }, "ConnectionStrings": { "DefaultConnection": "" + }, + "BlobStorageTestFile": { + "Endpoint": "", + "FilePath": "" } } diff --git a/Csag.WorkloadIdentity.Demo/packages.lock.json b/Csag.WorkloadIdentity.Demo/packages.lock.json index 92f975f..1d57f25 100644 --- a/Csag.WorkloadIdentity.Demo/packages.lock.json +++ b/Csag.WorkloadIdentity.Demo/packages.lock.json @@ -2,6 +2,16 @@ "version": 2, "dependencies": { "net10.0": { + "Azure.Storage.Blobs": { + "type": "Direct", + "requested": "[12.29.2, )", + "resolved": "12.29.2", + "contentHash": "NeZmk7bphspT2BYn8XaNCTV4/9vQG+ea79F85MBTi2o2WiXIIJNsRBtFyeBrzK2HIk476rfSqLNwPt4vXgOEkQ==", + "dependencies": { + "Azure.Core": "1.55.0", + "Azure.Storage.Common": "12.28.0" + } + }, "Microsoft.Data.SqlClient": { "type": "Direct", "requested": "[7.0.3, )", @@ -29,6 +39,15 @@ "Microsoft.EntityFrameworkCore.Relational": "10.0.12" } }, + "Azure.Storage.Common": { + "type": "Transitive", + "resolved": "12.28.0", + "contentHash": "5l8YhNrks38zKLGFW2BBFLwieyamH/xauABSASsdpZv79G0f+n2n22IjkRmUqCmALSupkwRHh2LOhbW3yj5Qgw==", + "dependencies": { + "Azure.Core": "1.55.0", + "System.IO.Hashing": "10.0.3" + } + }, "Google.Api.CommonProtos": { "type": "Transitive", "resolved": "2.17.0", @@ -273,6 +292,11 @@ "Microsoft.IdentityModel.Tokens": "8.16.0" } }, + "System.IO.Hashing": { + "type": "Transitive", + "resolved": "10.0.3", + "contentHash": "La6ICwsdTKhVX+LKN+pvFjQRR3LhLwq3uKdi2knjLzRyPYBSydF4cjXidYxIiTcDD6XVYdsBWQEI8ZxiZ/OdIg==" + }, "System.Management": { "type": "Transitive", "resolved": "7.0.2", diff --git a/Directory.Packages.props b/Directory.Packages.props index 7337a13..8969d5c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,6 +23,7 @@ + diff --git a/README.md b/README.md index ea2ca2b..c5e3d8b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A .NET library that lets an application running on Google Cloud connect to Azure | Project | Description | |---|---| | [`Csag.WorkloadIdentity`](./Csag.WorkloadIdentity) | The library, published as the [Csag.WorkloadIdentity](https://www.nuget.org/packages/Csag.WorkloadIdentity) NuGet package for `net8.0` and `net10.0`. Its [README](./Csag.WorkloadIdentity/README.md) is the package documentation; the [CHANGELOG](./Csag.WorkloadIdentity/CHANGELOG.md) is generated from changesets. | -| [`Csag.WorkloadIdentity.Demo`](./Csag.WorkloadIdentity.Demo) | An ASP.NET Core application for Cloud Run that reads from Azure SQL through the library. Its [README](./Csag.WorkloadIdentity.Demo/README.md) explains how to run it from source, in Docker and on Cloud Run. | +| [`Csag.WorkloadIdentity.Demo`](./Csag.WorkloadIdentity.Demo) | An ASP.NET Core application that reads from Azure SQL and, optionally, Blob Storage through the library, on Cloud Run or an Azure host. Its [README](./Csag.WorkloadIdentity.Demo/README.md) explains how to run it from source, in Docker and on Cloud Run. | | [`Csag.WorkloadIdentity.UnitTests`](./Csag.WorkloadIdentity.UnitTests) | xunit tests for the library, run on both target frameworks. | ## Quick start From 05d46b0021b3a281fb823d16e8bd7132248f298a Mon Sep 17 00:00:00 2001 From: Timothy Trowbridge Date: Fri, 18 Sep 2026 13:36:36 -0300 Subject: [PATCH 2/3] Demo: raise the central Azure.Core version to what Azure.Storage.Blobs requires Co-Authored-By: Claude Fable 5.1 --- Csag.WorkloadIdentity.Demo/packages.lock.json | 42 +++--- .../packages.lock.json | 124 ++++++++-------- Csag.WorkloadIdentity/packages.lock.json | 136 +++++++++--------- Directory.Packages.props | 2 +- 4 files changed, 158 insertions(+), 146 deletions(-) diff --git a/Csag.WorkloadIdentity.Demo/packages.lock.json b/Csag.WorkloadIdentity.Demo/packages.lock.json index 1d57f25..2219a10 100644 --- a/Csag.WorkloadIdentity.Demo/packages.lock.json +++ b/Csag.WorkloadIdentity.Demo/packages.lock.json @@ -136,8 +136,8 @@ }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "TV62UsrJZPX6gbt3c4WrtXh7bmaDIcMqf9uft1cc4L6gJXOU07hDGEh+bFQh/L2Az0R1WVOkiT66lFqS6G2NmA==" + "resolved": "10.0.10", + "contentHash": "TFI6OKYE1XZz4SGuTSH70c6SBdPpFktXsoa1gCxTr3mKrhmXirnvaS0tKz+J3ZWICEAmMpEGn59nO4ICtUpQXA==" }, "Microsoft.Bcl.Cryptography": { "type": "Transitive", @@ -191,18 +191,18 @@ }, "Microsoft.Identity.Client": { "type": "Transitive", - "resolved": "4.83.1", - "contentHash": "jOLIrZ3cynoqHLLO1cXplFFabrhrMEYs/EuKHvmCyrOm1axqiVFT6nCSnHxk7w5+d2BeQfCdM12Yf/0X7OeS1g==", + "resolved": "4.84.2", + "contentHash": "Va9FjmABSgj/lcUfHH0pBNQkmS7SNyepz2ERV7Yynp6QgEYpFdSqR6Vzy1WWipN8GS5pBHuXoZbqaDWuARCrHQ==", "dependencies": { "Microsoft.IdentityModel.Abstractions": "8.14.0" } }, "Microsoft.Identity.Client.Extensions.Msal": { "type": "Transitive", - "resolved": "4.83.1", - "contentHash": "I3k4J4Hj4KbLEFanjeUzzDOVecukETaTgEkJ7h2pP/Yazs6SLp6TVUTo/Eo+ptPXMwvc+iX7rBFtMSUrA7R+Mg==", + "resolved": "4.84.2", + "contentHash": "+D98LaU3dOu/Nzs6kgbBJapMvH7iwYcAeC99XwSkpmEadsPv6rozecOl9P6m4A3RfpOuPt9e6J6TwwUUp3+M4w==", "dependencies": { - "Microsoft.Identity.Client": "4.83.1", + "Microsoft.Identity.Client": "4.84.2", "System.Security.Cryptography.ProtectedData": "4.5.0" } }, @@ -264,10 +264,10 @@ }, "System.ClientModel": { "type": "Transitive", - "resolved": "1.10.0", - "contentHash": "lBEWs54F5Y5pZ9hC+8z4S/X76957ex+DPk7WecRHlbIHtrPfbRMMlOgI3iDn4Jpb3bSxvBnKaaHoD59auFjlBA==", + "resolved": "1.15.0", + "contentHash": "y6zQLInrpX+oGL1gXM04DrUSu1O1esWxt4rD/zrujTnzIFChn/noD1OiVVpTrU8ZvglwqO2Uw038svUR3SiAOg==", "dependencies": { - "System.Memory.Data": "10.0.3" + "System.Memory.Data": "10.0.9" } }, "System.CodeDom": { @@ -307,8 +307,8 @@ }, "System.Memory.Data": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "MaGhRfGunmrj/nHjtsi9XkhlYJ/ERGWrbA+BiSKNtGnAjc9XlG5EhAvak6VRcX5LYzPF6pBO8nJ613dTgzabig==" + "resolved": "10.0.10", + "contentHash": "roR+5AeOpflUxJQlqSfsZs/Bvi1uw4KBuTkIgF1e8l0RdalG4AUn84+sjJLclEfOV85gAjc8+Pp540qjDE5UIg==" }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", @@ -323,7 +323,7 @@ "csag.workloadidentity": { "type": "Project", "dependencies": { - "Azure.Core": "[1.53.0, )", + "Azure.Core": "[1.62.0, )", "Azure.Identity": "[1.21.0, )", "Google.Cloud.Iam.Credentials.V1": "[2.5.0, )", "Grpc.Core.Api": "[2.83.0, )" @@ -331,15 +331,15 @@ }, "Azure.Core": { "type": "CentralTransitive", - "requested": "[1.53.0, )", - "resolved": "1.53.0", - "contentHash": "x9c/toFMOtRrlTdFuE7rlGCVAduQzWVfKmLz5juj41zJAXEhYD5hluiUyyAEzJ6OxpBnKtiaBztzwpZITAVjtg==", + "requested": "[1.62.0, )", + "resolved": "1.62.0", + "contentHash": "C0ijs91ZW2uhF/VwGLXeayb+RwTWhDxKnrsmqPWg5HkYtPQ93mzaydyMCjAzE0AG5yHc1bpaPc+NPlZUgz3w8g==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "10.0.3", - "Microsoft.Identity.Client": "4.83.1", - "Microsoft.Identity.Client.Extensions.Msal": "4.83.1", - "System.ClientModel": "1.10.0", - "System.Memory.Data": "10.0.3" + "Microsoft.Bcl.AsyncInterfaces": "10.0.10", + "Microsoft.Identity.Client": "4.84.2", + "Microsoft.Identity.Client.Extensions.Msal": "4.84.2", + "System.ClientModel": "1.15.0", + "System.Memory.Data": "10.0.10" } }, "Azure.Identity": { diff --git a/Csag.WorkloadIdentity.UnitTests/packages.lock.json b/Csag.WorkloadIdentity.UnitTests/packages.lock.json index 5ea2442..1ea25bc 100644 --- a/Csag.WorkloadIdentity.UnitTests/packages.lock.json +++ b/Csag.WorkloadIdentity.UnitTests/packages.lock.json @@ -242,8 +242,8 @@ }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "TV62UsrJZPX6gbt3c4WrtXh7bmaDIcMqf9uft1cc4L6gJXOU07hDGEh+bFQh/L2Az0R1WVOkiT66lFqS6G2NmA==" + "resolved": "10.0.10", + "contentHash": "TFI6OKYE1XZz4SGuTSH70c6SBdPpFktXsoa1gCxTr3mKrhmXirnvaS0tKz+J3ZWICEAmMpEGn59nO4ICtUpQXA==" }, "Microsoft.CodeCoverage": { "type": "Transitive", @@ -421,18 +421,18 @@ }, "Microsoft.Identity.Client": { "type": "Transitive", - "resolved": "4.83.1", - "contentHash": "jOLIrZ3cynoqHLLO1cXplFFabrhrMEYs/EuKHvmCyrOm1axqiVFT6nCSnHxk7w5+d2BeQfCdM12Yf/0X7OeS1g==", + "resolved": "4.84.2", + "contentHash": "Va9FjmABSgj/lcUfHH0pBNQkmS7SNyepz2ERV7Yynp6QgEYpFdSqR6Vzy1WWipN8GS5pBHuXoZbqaDWuARCrHQ==", "dependencies": { "Microsoft.IdentityModel.Abstractions": "8.14.0" } }, "Microsoft.Identity.Client.Extensions.Msal": { "type": "Transitive", - "resolved": "4.83.1", - "contentHash": "I3k4J4Hj4KbLEFanjeUzzDOVecukETaTgEkJ7h2pP/Yazs6SLp6TVUTo/Eo+ptPXMwvc+iX7rBFtMSUrA7R+Mg==", + "resolved": "4.84.2", + "contentHash": "+D98LaU3dOu/Nzs6kgbBJapMvH7iwYcAeC99XwSkpmEadsPv6rozecOl9P6m4A3RfpOuPt9e6J6TwwUUp3+M4w==", "dependencies": { - "Microsoft.Identity.Client": "4.83.1", + "Microsoft.Identity.Client": "4.84.2", "System.Security.Cryptography.ProtectedData": "4.5.0" } }, @@ -471,13 +471,13 @@ }, "System.ClientModel": { "type": "Transitive", - "resolved": "1.10.0", - "contentHash": "lBEWs54F5Y5pZ9hC+8z4S/X76957ex+DPk7WecRHlbIHtrPfbRMMlOgI3iDn4Jpb3bSxvBnKaaHoD59auFjlBA==", + "resolved": "1.15.0", + "contentHash": "y6zQLInrpX+oGL1gXM04DrUSu1O1esWxt4rD/zrujTnzIFChn/noD1OiVVpTrU8ZvglwqO2Uw038svUR3SiAOg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "System.Memory.Data": "10.0.3" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.9", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.9", + "Microsoft.Extensions.Logging.Abstractions": "10.0.9", + "System.Memory.Data": "10.0.9" } }, "System.CodeDom": { @@ -500,8 +500,8 @@ }, "System.Memory.Data": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "MaGhRfGunmrj/nHjtsi9XkhlYJ/ERGWrbA+BiSKNtGnAjc9XlG5EhAvak6VRcX5LYzPF6pBO8nJ613dTgzabig==" + "resolved": "10.0.10", + "contentHash": "roR+5AeOpflUxJQlqSfsZs/Bvi1uw4KBuTkIgF1e8l0RdalG4AUn84+sjJLclEfOV85gAjc8+Pp540qjDE5UIg==" }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", @@ -551,7 +551,7 @@ "csag.workloadidentity": { "type": "Project", "dependencies": { - "Azure.Core": "[1.53.0, )", + "Azure.Core": "[1.62.0, )", "Azure.Identity": "[1.21.0, )", "Google.Cloud.Iam.Credentials.V1": "[2.5.0, )", "Grpc.Core.Api": "[2.83.0, )", @@ -565,17 +565,17 @@ }, "Azure.Core": { "type": "CentralTransitive", - "requested": "[1.53.0, )", - "resolved": "1.53.0", - "contentHash": "x9c/toFMOtRrlTdFuE7rlGCVAduQzWVfKmLz5juj41zJAXEhYD5hluiUyyAEzJ6OxpBnKtiaBztzwpZITAVjtg==", + "requested": "[1.62.0, )", + "resolved": "1.62.0", + "contentHash": "C0ijs91ZW2uhF/VwGLXeayb+RwTWhDxKnrsmqPWg5HkYtPQ93mzaydyMCjAzE0AG5yHc1bpaPc+NPlZUgz3w8g==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", - "Microsoft.Identity.Client": "4.83.1", - "Microsoft.Identity.Client.Extensions.Msal": "4.83.1", - "System.ClientModel": "1.10.0", - "System.Memory.Data": "10.0.3" + "Microsoft.Bcl.AsyncInterfaces": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.10", + "Microsoft.Identity.Client": "4.84.2", + "Microsoft.Identity.Client.Extensions.Msal": "4.84.2", + "System.ClientModel": "1.15.0", + "System.Memory.Data": "10.0.10" } }, "Azure.Identity": { @@ -904,8 +904,8 @@ }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "TV62UsrJZPX6gbt3c4WrtXh7bmaDIcMqf9uft1cc4L6gJXOU07hDGEh+bFQh/L2Az0R1WVOkiT66lFqS6G2NmA==" + "resolved": "10.0.10", + "contentHash": "TFI6OKYE1XZz4SGuTSH70c6SBdPpFktXsoa1gCxTr3mKrhmXirnvaS0tKz+J3ZWICEAmMpEGn59nO4ICtUpQXA==" }, "Microsoft.CodeCoverage": { "type": "Transitive", @@ -1087,18 +1087,18 @@ }, "Microsoft.Identity.Client": { "type": "Transitive", - "resolved": "4.83.1", - "contentHash": "jOLIrZ3cynoqHLLO1cXplFFabrhrMEYs/EuKHvmCyrOm1axqiVFT6nCSnHxk7w5+d2BeQfCdM12Yf/0X7OeS1g==", + "resolved": "4.84.2", + "contentHash": "Va9FjmABSgj/lcUfHH0pBNQkmS7SNyepz2ERV7Yynp6QgEYpFdSqR6Vzy1WWipN8GS5pBHuXoZbqaDWuARCrHQ==", "dependencies": { "Microsoft.IdentityModel.Abstractions": "8.14.0" } }, "Microsoft.Identity.Client.Extensions.Msal": { "type": "Transitive", - "resolved": "4.83.1", - "contentHash": "I3k4J4Hj4KbLEFanjeUzzDOVecukETaTgEkJ7h2pP/Yazs6SLp6TVUTo/Eo+ptPXMwvc+iX7rBFtMSUrA7R+Mg==", + "resolved": "4.84.2", + "contentHash": "+D98LaU3dOu/Nzs6kgbBJapMvH7iwYcAeC99XwSkpmEadsPv6rozecOl9P6m4A3RfpOuPt9e6J6TwwUUp3+M4w==", "dependencies": { - "Microsoft.Identity.Client": "4.83.1", + "Microsoft.Identity.Client": "4.84.2", "System.Security.Cryptography.ProtectedData": "4.5.0" } }, @@ -1137,15 +1137,16 @@ }, "System.ClientModel": { "type": "Transitive", - "resolved": "1.10.0", - "contentHash": "lBEWs54F5Y5pZ9hC+8z4S/X76957ex+DPk7WecRHlbIHtrPfbRMMlOgI3iDn4Jpb3bSxvBnKaaHoD59auFjlBA==", + "resolved": "1.15.0", + "contentHash": "y6zQLInrpX+oGL1gXM04DrUSu1O1esWxt4rD/zrujTnzIFChn/noD1OiVVpTrU8ZvglwqO2Uw038svUR3SiAOg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "System.Diagnostics.DiagnosticSource": "10.0.3", - "System.Memory.Data": "10.0.3", - "System.Text.Json": "10.0.3" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.9", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.9", + "Microsoft.Extensions.Logging.Abstractions": "10.0.9", + "System.Diagnostics.DiagnosticSource": "10.0.9", + "System.Memory.Data": "10.0.9", + "System.Net.ServerSentEvents": "10.0.9", + "System.Text.Json": "10.0.9" } }, "System.CodeDom": { @@ -1178,12 +1179,17 @@ }, "System.Memory.Data": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "MaGhRfGunmrj/nHjtsi9XkhlYJ/ERGWrbA+BiSKNtGnAjc9XlG5EhAvak6VRcX5LYzPF6pBO8nJ613dTgzabig==", + "resolved": "10.0.10", + "contentHash": "roR+5AeOpflUxJQlqSfsZs/Bvi1uw4KBuTkIgF1e8l0RdalG4AUn84+sjJLclEfOV85gAjc8+Pp540qjDE5UIg==", "dependencies": { - "System.Text.Json": "10.0.3" + "System.Text.Json": "10.0.10" } }, + "System.Net.ServerSentEvents": { + "type": "Transitive", + "resolved": "10.0.9", + "contentHash": "ZO/IpJ2Zl3fXoRr2LQngRd2LCX6QHksz+N8Xl4f2UktrC/eQk4HtXpvuk/C/clmbTUuH1b13YVc8tC0mrYg81Q==" + }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "4.5.0", @@ -1246,7 +1252,7 @@ "csag.workloadidentity": { "type": "Project", "dependencies": { - "Azure.Core": "[1.53.0, )", + "Azure.Core": "[1.62.0, )", "Azure.Identity": "[1.21.0, )", "Google.Cloud.Iam.Credentials.V1": "[2.5.0, )", "Grpc.Core.Api": "[2.83.0, )", @@ -1260,20 +1266,20 @@ }, "Azure.Core": { "type": "CentralTransitive", - "requested": "[1.53.0, )", - "resolved": "1.53.0", - "contentHash": "x9c/toFMOtRrlTdFuE7rlGCVAduQzWVfKmLz5juj41zJAXEhYD5hluiUyyAEzJ6OxpBnKtiaBztzwpZITAVjtg==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", - "Microsoft.Identity.Client": "4.83.1", - "Microsoft.Identity.Client.Extensions.Msal": "4.83.1", - "System.ClientModel": "1.10.0", - "System.Diagnostics.DiagnosticSource": "10.0.3", - "System.Memory.Data": "10.0.3", - "System.Text.Encodings.Web": "10.0.3", - "System.Text.Json": "10.0.3" + "requested": "[1.62.0, )", + "resolved": "1.62.0", + "contentHash": "C0ijs91ZW2uhF/VwGLXeayb+RwTWhDxKnrsmqPWg5HkYtPQ93mzaydyMCjAzE0AG5yHc1bpaPc+NPlZUgz3w8g==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.10", + "Microsoft.Identity.Client": "4.84.2", + "Microsoft.Identity.Client.Extensions.Msal": "4.84.2", + "System.ClientModel": "1.15.0", + "System.Diagnostics.DiagnosticSource": "10.0.10", + "System.Memory.Data": "10.0.10", + "System.Text.Encodings.Web": "10.0.10", + "System.Text.Json": "10.0.10" } }, "Azure.Identity": { diff --git a/Csag.WorkloadIdentity/packages.lock.json b/Csag.WorkloadIdentity/packages.lock.json index 5869dd1..cf3ab9f 100644 --- a/Csag.WorkloadIdentity/packages.lock.json +++ b/Csag.WorkloadIdentity/packages.lock.json @@ -4,17 +4,17 @@ "net10.0": { "Azure.Core": { "type": "Direct", - "requested": "[1.53.0, )", - "resolved": "1.53.0", - "contentHash": "x9c/toFMOtRrlTdFuE7rlGCVAduQzWVfKmLz5juj41zJAXEhYD5hluiUyyAEzJ6OxpBnKtiaBztzwpZITAVjtg==", + "requested": "[1.62.0, )", + "resolved": "1.62.0", + "contentHash": "C0ijs91ZW2uhF/VwGLXeayb+RwTWhDxKnrsmqPWg5HkYtPQ93mzaydyMCjAzE0AG5yHc1bpaPc+NPlZUgz3w8g==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", - "Microsoft.Identity.Client": "4.83.1", - "Microsoft.Identity.Client.Extensions.Msal": "4.83.1", - "System.ClientModel": "1.10.0", - "System.Memory.Data": "10.0.3" + "Microsoft.Bcl.AsyncInterfaces": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.10", + "Microsoft.Identity.Client": "4.84.2", + "Microsoft.Identity.Client.Extensions.Msal": "4.84.2", + "System.ClientModel": "1.15.0", + "System.Memory.Data": "10.0.10" } }, "Azure.Identity": { @@ -201,8 +201,8 @@ }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "TV62UsrJZPX6gbt3c4WrtXh7bmaDIcMqf9uft1cc4L6gJXOU07hDGEh+bFQh/L2Az0R1WVOkiT66lFqS6G2NmA==" + "resolved": "10.0.10", + "contentHash": "TFI6OKYE1XZz4SGuTSH70c6SBdPpFktXsoa1gCxTr3mKrhmXirnvaS0tKz+J3ZWICEAmMpEGn59nO4ICtUpQXA==" }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", @@ -237,18 +237,18 @@ }, "Microsoft.Identity.Client": { "type": "Transitive", - "resolved": "4.83.1", - "contentHash": "jOLIrZ3cynoqHLLO1cXplFFabrhrMEYs/EuKHvmCyrOm1axqiVFT6nCSnHxk7w5+d2BeQfCdM12Yf/0X7OeS1g==", + "resolved": "4.84.2", + "contentHash": "Va9FjmABSgj/lcUfHH0pBNQkmS7SNyepz2ERV7Yynp6QgEYpFdSqR6Vzy1WWipN8GS5pBHuXoZbqaDWuARCrHQ==", "dependencies": { "Microsoft.IdentityModel.Abstractions": "8.14.0" } }, "Microsoft.Identity.Client.Extensions.Msal": { "type": "Transitive", - "resolved": "4.83.1", - "contentHash": "I3k4J4Hj4KbLEFanjeUzzDOVecukETaTgEkJ7h2pP/Yazs6SLp6TVUTo/Eo+ptPXMwvc+iX7rBFtMSUrA7R+Mg==", + "resolved": "4.84.2", + "contentHash": "+D98LaU3dOu/Nzs6kgbBJapMvH7iwYcAeC99XwSkpmEadsPv6rozecOl9P6m4A3RfpOuPt9e6J6TwwUUp3+M4w==", "dependencies": { - "Microsoft.Identity.Client": "4.83.1", + "Microsoft.Identity.Client": "4.84.2", "System.Security.Cryptography.ProtectedData": "4.5.0" } }, @@ -274,13 +274,13 @@ }, "System.ClientModel": { "type": "Transitive", - "resolved": "1.10.0", - "contentHash": "lBEWs54F5Y5pZ9hC+8z4S/X76957ex+DPk7WecRHlbIHtrPfbRMMlOgI3iDn4Jpb3bSxvBnKaaHoD59auFjlBA==", + "resolved": "1.15.0", + "contentHash": "y6zQLInrpX+oGL1gXM04DrUSu1O1esWxt4rD/zrujTnzIFChn/noD1OiVVpTrU8ZvglwqO2Uw038svUR3SiAOg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "System.Memory.Data": "10.0.3" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.9", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.9", + "Microsoft.Extensions.Logging.Abstractions": "10.0.9", + "System.Memory.Data": "10.0.9" } }, "System.CodeDom": { @@ -298,8 +298,8 @@ }, "System.Memory.Data": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "MaGhRfGunmrj/nHjtsi9XkhlYJ/ERGWrbA+BiSKNtGnAjc9XlG5EhAvak6VRcX5LYzPF6pBO8nJ613dTgzabig==" + "resolved": "10.0.10", + "contentHash": "roR+5AeOpflUxJQlqSfsZs/Bvi1uw4KBuTkIgF1e8l0RdalG4AUn84+sjJLclEfOV85gAjc8+Pp540qjDE5UIg==" }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", @@ -320,20 +320,20 @@ "net8.0": { "Azure.Core": { "type": "Direct", - "requested": "[1.53.0, )", - "resolved": "1.53.0", - "contentHash": "x9c/toFMOtRrlTdFuE7rlGCVAduQzWVfKmLz5juj41zJAXEhYD5hluiUyyAEzJ6OxpBnKtiaBztzwpZITAVjtg==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", - "Microsoft.Identity.Client": "4.83.1", - "Microsoft.Identity.Client.Extensions.Msal": "4.83.1", - "System.ClientModel": "1.10.0", - "System.Diagnostics.DiagnosticSource": "10.0.3", - "System.Memory.Data": "10.0.3", - "System.Text.Encodings.Web": "10.0.3", - "System.Text.Json": "10.0.3" + "requested": "[1.62.0, )", + "resolved": "1.62.0", + "contentHash": "C0ijs91ZW2uhF/VwGLXeayb+RwTWhDxKnrsmqPWg5HkYtPQ93mzaydyMCjAzE0AG5yHc1bpaPc+NPlZUgz3w8g==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.10", + "Microsoft.Identity.Client": "4.84.2", + "Microsoft.Identity.Client.Extensions.Msal": "4.84.2", + "System.ClientModel": "1.15.0", + "System.Diagnostics.DiagnosticSource": "10.0.10", + "System.Memory.Data": "10.0.10", + "System.Text.Encodings.Web": "10.0.10", + "System.Text.Json": "10.0.10" } }, "Azure.Identity": { @@ -521,8 +521,8 @@ }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "TV62UsrJZPX6gbt3c4WrtXh7bmaDIcMqf9uft1cc4L6gJXOU07hDGEh+bFQh/L2Az0R1WVOkiT66lFqS6G2NmA==" + "resolved": "10.0.10", + "contentHash": "TFI6OKYE1XZz4SGuTSH70c6SBdPpFktXsoa1gCxTr3mKrhmXirnvaS0tKz+J3ZWICEAmMpEGn59nO4ICtUpQXA==" }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", @@ -558,18 +558,18 @@ }, "Microsoft.Identity.Client": { "type": "Transitive", - "resolved": "4.83.1", - "contentHash": "jOLIrZ3cynoqHLLO1cXplFFabrhrMEYs/EuKHvmCyrOm1axqiVFT6nCSnHxk7w5+d2BeQfCdM12Yf/0X7OeS1g==", + "resolved": "4.84.2", + "contentHash": "Va9FjmABSgj/lcUfHH0pBNQkmS7SNyepz2ERV7Yynp6QgEYpFdSqR6Vzy1WWipN8GS5pBHuXoZbqaDWuARCrHQ==", "dependencies": { "Microsoft.IdentityModel.Abstractions": "8.14.0" } }, "Microsoft.Identity.Client.Extensions.Msal": { "type": "Transitive", - "resolved": "4.83.1", - "contentHash": "I3k4J4Hj4KbLEFanjeUzzDOVecukETaTgEkJ7h2pP/Yazs6SLp6TVUTo/Eo+ptPXMwvc+iX7rBFtMSUrA7R+Mg==", + "resolved": "4.84.2", + "contentHash": "+D98LaU3dOu/Nzs6kgbBJapMvH7iwYcAeC99XwSkpmEadsPv6rozecOl9P6m4A3RfpOuPt9e6J6TwwUUp3+M4w==", "dependencies": { - "Microsoft.Identity.Client": "4.83.1", + "Microsoft.Identity.Client": "4.84.2", "System.Security.Cryptography.ProtectedData": "4.5.0" } }, @@ -595,15 +595,16 @@ }, "System.ClientModel": { "type": "Transitive", - "resolved": "1.10.0", - "contentHash": "lBEWs54F5Y5pZ9hC+8z4S/X76957ex+DPk7WecRHlbIHtrPfbRMMlOgI3iDn4Jpb3bSxvBnKaaHoD59auFjlBA==", + "resolved": "1.15.0", + "contentHash": "y6zQLInrpX+oGL1gXM04DrUSu1O1esWxt4rD/zrujTnzIFChn/noD1OiVVpTrU8ZvglwqO2Uw038svUR3SiAOg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "System.Diagnostics.DiagnosticSource": "10.0.3", - "System.Memory.Data": "10.0.3", - "System.Text.Json": "10.0.3" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.9", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.9", + "Microsoft.Extensions.Logging.Abstractions": "10.0.9", + "System.Diagnostics.DiagnosticSource": "10.0.9", + "System.Memory.Data": "10.0.9", + "System.Net.ServerSentEvents": "10.0.9", + "System.Text.Json": "10.0.9" } }, "System.CodeDom": { @@ -618,8 +619,8 @@ }, "System.IO.Pipelines": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "WMxiA2jGdHnRBmoVK55YUq5VPaxW0Sg2frPtXV+urUMvpqHIga6lleV/YuryHIuGsAKVjQAjv6PrQ6IJpoLohQ==" + "resolved": "10.0.10", + "contentHash": "7WX0W96y3dpQdYG4sEGdh38g3/0lOD4/dKbn2rRVOVzKhzoZUn2gKNIKaFeKWs8RCbpFfmmEWsRhSy95hMpvqA==" }, "System.Management": { "type": "Transitive", @@ -631,12 +632,17 @@ }, "System.Memory.Data": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "MaGhRfGunmrj/nHjtsi9XkhlYJ/ERGWrbA+BiSKNtGnAjc9XlG5EhAvak6VRcX5LYzPF6pBO8nJ613dTgzabig==", + "resolved": "10.0.10", + "contentHash": "roR+5AeOpflUxJQlqSfsZs/Bvi1uw4KBuTkIgF1e8l0RdalG4AUn84+sjJLclEfOV85gAjc8+Pp540qjDE5UIg==", "dependencies": { - "System.Text.Json": "10.0.3" + "System.Text.Json": "10.0.10" } }, + "System.Net.ServerSentEvents": { + "type": "Transitive", + "resolved": "10.0.9", + "contentHash": "ZO/IpJ2Zl3fXoRr2LQngRd2LCX6QHksz+N8Xl4f2UktrC/eQk4HtXpvuk/C/clmbTUuH1b13YVc8tC0mrYg81Q==" + }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "4.5.0", @@ -644,16 +650,16 @@ }, "System.Text.Encodings.Web": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "l8QNBPp92bVzl9Kw8nNtm1uYRNNhUrdulZjM4a8YK/QGNa8z9utKsC0bDoPB+Vq8LOlbD3fIyGlabtz80jT7cw==" + "resolved": "10.0.10", + "contentHash": "o16m2YpDN/pjHsnxf9pTGwkpcuvjW8v1/wGUwJtM1c3QZUKm7ZEO/eYRJg7iIx6GxS2Zv9lAMHpiQwHDdgqauA==" }, "System.Text.Json": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "NTUt9DL+maqbgrIYCAmeZUbX0NoXaueySyjW/bdOlFdSUDC1l51XnsbVEuj5tuad12vdq5Sviskp9uMVGgCNLw==", + "resolved": "10.0.10", + "contentHash": "bmsO6UdYtBdtn32zYXfsh7KlyTIzV/3V9hdT9RIb4pXKgYOsNxXR+VbWigNwBtNFVGYGm6Hwmqw5a+/IWFd36Q==", "dependencies": { - "System.IO.Pipelines": "10.0.3", - "System.Text.Encodings.Web": "10.0.3" + "System.IO.Pipelines": "10.0.10", + "System.Text.Encodings.Web": "10.0.10" } }, "Microsoft.Extensions.Configuration": { diff --git a/Directory.Packages.props b/Directory.Packages.props index 8969d5c..dcc470f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + From f3dd5edbee3e8454defe145322c0871aaad3d496 Mon Sep 17 00:00:00 2001 From: Timothy Trowbridge Date: Fri, 18 Sep 2026 14:02:34 -0300 Subject: [PATCH 3/3] Demo: make the optional BlobStorage example valid JSON when uncommented; run the local container as the host user Co-Authored-By: Claude Fable 5.1 --- Csag.WorkloadIdentity.Demo/appsettings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Csag.WorkloadIdentity.Demo/appsettings.json b/Csag.WorkloadIdentity.Demo/appsettings.json index 6dd45c4..57dd856 100644 --- a/Csag.WorkloadIdentity.Demo/appsettings.json +++ b/Csag.WorkloadIdentity.Demo/appsettings.json @@ -16,7 +16,8 @@ } } // Optional. Together with "BlobStorageTestFile" below, this makes /test also download a blob. - // "BlobStorage": { + // Uncomment the whole block, including the leading comma that separates it from "AzureSql": + // ,"BlobStorage": { // "Provider": "Google", // "Google": { // "TenantId": "",