From 422f75e89bafaa15286effebc620a28385d89d00 Mon Sep 17 00:00:00 2001 From: Timothy Trowbridge Date: Thu, 17 Sep 2026 22:16:44 -0300 Subject: [PATCH] Rename package and namespaces to Csag.WorkloadIdentity The library is about to grow beyond Azure SQL (Blob Storage, Azure Managed Identity as an alternative to Google federation), so the package, root namespace, project, solution and folder names move from Csag.AzureSqlFederatedIdentity to Csag.WorkloadIdentity, and the changesets package becomes @neolution-ch/csag-workload-identity. The configuration section key changes accordingly. Nothing has been published under the intermediate name. Co-Authored-By: Claude Fable 5.1 --- .changeset/build-hygiene.md | 2 +- .changeset/config.json | 2 +- .changeset/docs-usage-and-guides.md | 2 +- .changeset/rename-to-csag.md | 6 ++-- .changeset/token-provider-hardening.md | 4 +-- .dockerignore | 4 +-- .github/workflows/ci.yml | 10 +++--- .github/workflows/dependabot-changeset.yml | 2 +- .github/workflows/nuget-publish.yml | 6 ++-- CONTRIBUTING.md | 10 +++--- .../Dockerfile | 26 -------------- Csag.AzureSqlFederatedIdentity/package.json | 5 --- .../Csag.WorkloadIdentity.Demo.csproj | 2 +- .../Database/AppDbContext.cs | 4 +-- .../Database/AppDbContextFactory.cs | 4 +-- .../Database/IAppDbContextFactory.cs | 2 +- Csag.WorkloadIdentity.Demo/Dockerfile | 26 ++++++++++++++ .../Entities/TestEntity.cs | 2 +- .../Program.cs | 4 +-- .../Properties/launchSettings.json | 0 .../README.md | 28 +++++++-------- .../appsettings.Development.json | 0 .../appsettings.json | 2 +- .../packages.lock.json | 2 +- .../wwwroot/favicon.ico | Bin ...lFederatedIdentityOptionsValidatorTests.cs | 4 +-- .../AzureSqlTokenExchangerTests.cs | 6 ++-- .../AzureSqlTokenProviderTests.cs | 8 ++--- .../AzureSqlTokenRefreshServiceTests.cs | 10 +++--- .../Csag.WorkloadIdentity.UnitTests.csproj | 2 +- ...dentityServiceCollectionExtensionsTests.cs | 8 ++--- .../GoogleIdTokenProviderTests.cs | 6 ++-- .../packages.lock.json | 4 +-- ...dIdentity.sln => Csag.WorkloadIdentity.sln | 6 ++-- .../Abstractions/IAzureSqlTokenExchanger.cs | 2 +- .../Abstractions/IAzureSqlTokenProvider.cs | 2 +- .../Abstractions/IGoogleIdTokenProvider.cs | 2 +- .../AzureSqlTokenProvider.cs | 8 ++--- .../CHANGELOG.md | 2 +- .../Csag.WorkloadIdentity.csproj | 2 +- ...atedIdentityServiceCollectionExtensions.cs | 10 +++--- .../Internal/AzureSqlTokenExchanger.cs | 4 +-- .../ClientAssertionCredentialFactory.cs | 2 +- .../Internal/GoogleIdTokenProvider.cs | 6 ++-- .../Internal/IAzureSqlTokenRefresher.cs | 2 +- .../IClientAssertionCredentialFactory.cs | 2 +- .../Internal/IIamCredentialsClientFactory.cs | 2 +- .../Internal/IamCredentialsClientFactory.cs | 2 +- .../Services/AzureSqlTokenRefreshService.cs | 6 ++-- .../AzureSqlFederatedIdentityOptions.cs | 4 +-- ...ureSqlFederatedIdentityOptionsValidator.cs | 2 +- .../Options/GoogleOptions.cs | 2 +- .../README.md | 32 +++++++++--------- .../icon.png | Bin Csag.WorkloadIdentity/package.json | 5 +++ .../packages.lock.json | 0 README.md | 16 ++++----- SECURITY.md | 4 +-- docs/cloud-identity-setup.md | 26 +++++++------- package-lock.json | 14 ++++---- package.json | 4 +-- 61 files changed, 186 insertions(+), 186 deletions(-) delete mode 100644 Csag.AzureSqlFederatedIdentity.Demo/Dockerfile delete mode 100644 Csag.AzureSqlFederatedIdentity/package.json rename Csag.AzureSqlFederatedIdentity.Demo/Csag.AzureSqlFederatedIdentity.Demo.csproj => Csag.WorkloadIdentity.Demo/Csag.WorkloadIdentity.Demo.csproj (80%) rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/Database/AppDbContext.cs (84%) rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/Database/AppDbContextFactory.cs (93%) rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/Database/IAppDbContextFactory.cs (73%) create mode 100644 Csag.WorkloadIdentity.Demo/Dockerfile rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/Entities/TestEntity.cs (70%) rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/Program.cs (94%) rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/Properties/launchSettings.json (100%) rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/README.md (74%) rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/appsettings.Development.json (100%) rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/appsettings.json (88%) rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/packages.lock.json (99%) rename {Csag.AzureSqlFederatedIdentity.Demo => Csag.WorkloadIdentity.Demo}/wwwroot/favicon.ico (100%) rename {Csag.AzureSqlFederatedIdentity.UnitTests => Csag.WorkloadIdentity.UnitTests}/AzureSqlFederatedIdentityOptionsValidatorTests.cs (97%) rename {Csag.AzureSqlFederatedIdentity.UnitTests => Csag.WorkloadIdentity.UnitTests}/AzureSqlTokenExchangerTests.cs (97%) rename {Csag.AzureSqlFederatedIdentity.UnitTests => Csag.WorkloadIdentity.UnitTests}/AzureSqlTokenProviderTests.cs (98%) rename {Csag.AzureSqlFederatedIdentity.UnitTests => Csag.WorkloadIdentity.UnitTests}/AzureSqlTokenRefreshServiceTests.cs (98%) rename Csag.AzureSqlFederatedIdentity.UnitTests/Csag.AzureSqlFederatedIdentity.UnitTests.csproj => Csag.WorkloadIdentity.UnitTests/Csag.WorkloadIdentity.UnitTests.csproj (93%) rename {Csag.AzureSqlFederatedIdentity.UnitTests => Csag.WorkloadIdentity.UnitTests}/FederatedIdentityServiceCollectionExtensionsTests.cs (97%) rename {Csag.AzureSqlFederatedIdentity.UnitTests => Csag.WorkloadIdentity.UnitTests}/GoogleIdTokenProviderTests.cs (98%) rename {Csag.AzureSqlFederatedIdentity.UnitTests => Csag.WorkloadIdentity.UnitTests}/packages.lock.json (99%) rename Csag.AzureSqlFederatedIdentity.sln => Csag.WorkloadIdentity.sln (71%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Abstractions/IAzureSqlTokenExchanger.cs (93%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Abstractions/IAzureSqlTokenProvider.cs (90%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Abstractions/IGoogleIdTokenProvider.cs (92%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/AzureSqlTokenProvider.cs (97%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/CHANGELOG.md (87%) rename Csag.AzureSqlFederatedIdentity/Csag.AzureSqlFederatedIdentity.csproj => Csag.WorkloadIdentity/Csag.WorkloadIdentity.csproj (95%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/FederatedIdentityServiceCollectionExtensions.cs (95%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Internal/AzureSqlTokenExchanger.cs (97%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Internal/ClientAssertionCredentialFactory.cs (90%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Internal/GoogleIdTokenProvider.cs (97%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Internal/IAzureSqlTokenRefresher.cs (93%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Internal/IClientAssertionCredentialFactory.cs (94%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Internal/IIamCredentialsClientFactory.cs (92%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Internal/IamCredentialsClientFactory.cs (90%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Internal/Services/AzureSqlTokenRefreshService.cs (97%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Options/AzureSqlFederatedIdentityOptions.cs (95%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Options/AzureSqlFederatedIdentityOptionsValidator.cs (97%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/Options/GoogleOptions.cs (86%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/README.md (86%) rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/icon.png (100%) create mode 100644 Csag.WorkloadIdentity/package.json rename {Csag.AzureSqlFederatedIdentity => Csag.WorkloadIdentity}/packages.lock.json (100%) diff --git a/.changeset/build-hygiene.md b/.changeset/build-hygiene.md index 83ba69d..46fb121 100644 --- a/.changeset/build-hygiene.md +++ b/.changeset/build-hygiene.md @@ -1,5 +1,5 @@ --- -"@neolution-ch/csag-azure-sql-federated-identity": minor +"@neolution-ch/csag-workload-identity": minor --- Target `net8.0` and `net10.0`, and tidy the dependency surface. diff --git a/.changeset/config.json b/.changeset/config.json index 8d9326d..2d2f662 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -5,7 +5,7 @@ { "repo": "neolution-ch/Neolution.AzureSqlFederatedIdentity" } ], "commit": false, - "fixed": [["@neolution-ch/csag-azure-sql-federated-identity"]], + "fixed": [["@neolution-ch/csag-workload-identity"]], "linked": [], "access": "restricted", "baseBranch": "main", diff --git a/.changeset/docs-usage-and-guides.md b/.changeset/docs-usage-and-guides.md index aeba23b..8f70183 100644 --- a/.changeset/docs-usage-and-guides.md +++ b/.changeset/docs-usage-and-guides.md @@ -1,5 +1,5 @@ --- -"@neolution-ch/csag-azure-sql-federated-identity": patch +"@neolution-ch/csag-workload-identity": patch --- Rewrite the package README as a self-contained quickstart: every configuration key including `RefreshAheadWindow` and `EnableBackgroundRefresh`, all three `AddAzureSqlFederatedIdentity` overloads, obtaining the token from `IAzureSqlTokenProvider` and assigning it to `SqlConnection.AccessToken` with plain ADO.NET and with EF Core, the prerequisites on the Google and Microsoft side, and a troubleshooting table. Links are absolute so they work on nuget.org. diff --git a/.changeset/rename-to-csag.md b/.changeset/rename-to-csag.md index 504775d..7695bcd 100644 --- a/.changeset/rename-to-csag.md +++ b/.changeset/rename-to-csag.md @@ -1,7 +1,7 @@ --- -"@neolution-ch/csag-azure-sql-federated-identity": minor +"@neolution-ch/csag-workload-identity": minor --- -Rename the package to `Csag.AzureSqlFederatedIdentity` (previously `Neolution.AzureSqlFederatedIdentity`). +Rename the package to `Csag.WorkloadIdentity` (previously `Neolution.AzureSqlFederatedIdentity`). -The root namespace and the configuration section key change to `Csag.AzureSqlFederatedIdentity`; update `using` directives and `appsettings.json` accordingly. NuGet metadata now names collana solutions AG. +The root namespace and the configuration section key change to `Csag.WorkloadIdentity`; update `using` directives and `appsettings.json` accordingly. NuGet metadata now names collana solutions AG. diff --git a/.changeset/token-provider-hardening.md b/.changeset/token-provider-hardening.md index d7c364c..8975279 100644 --- a/.changeset/token-provider-hardening.md +++ b/.changeset/token-provider-hardening.md @@ -1,5 +1,5 @@ --- -"@neolution-ch/csag-azure-sql-federated-identity": minor +"@neolution-ch/csag-workload-identity": minor --- Harden token acquisition, refresh and registration. @@ -9,6 +9,6 @@ Harden token acquisition, refresh and registration. - The background refresh service refreshes ahead of the token's own expiry (`ExpiresOn` minus `RefreshAheadWindow`) rather than on a fixed interval, retries with exponential backoff when an exchange fails, and stops quietly on host shutdown. It can be turned off with `EnableBackgroundRefresh = false`. - New options: `RefreshAheadWindow` (default five minutes; must be positive) replaces the hard-coded skew and interval, and `EnableBackgroundRefresh` (default `true`). A token that arrives with less than the window remaining is still used and replaced on the next call. - The `Google` configuration section is now required; a missing section fails validation with a clear message instead of an `ArgumentNullException` when the provider is resolved. -- Options are bound with `BindConfiguration` and validated at host startup (`ValidateOnStart`). `AddAzureSqlFederatedIdentity()` without arguments binds the `Csag.AzureSqlFederatedIdentity` section from the host configuration; the section name is exposed as `AzureSqlFederatedIdentityOptions.ConfigurationSectionName`. The `Action` and `IConfiguration` overloads remain. +- Options are bound with `BindConfiguration` and validated at host startup (`ValidateOnStart`). `AddAzureSqlFederatedIdentity()` without arguments binds the `Csag.WorkloadIdentity` section from the host configuration; the section name is exposed as `AzureSqlFederatedIdentityOptions.ConfigurationSectionName`. The `Action` and `IConfiguration` overloads remain. - Services are registered with `TryAdd*`, so a consumer-registered `IAzureSqlTokenProvider` (or any other service in the pipeline) takes precedence and repeated registration calls do not duplicate services or the hosted service. A substituted provider is left alone by the background refresh service. - The Google IAM Credentials client and the Azure `ClientAssertionCredential` are created once and reused across token exchanges. diff --git a/.dockerignore b/.dockerignore index 764002b..d770697 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,4 @@ -# Context for Csag.AzureSqlFederatedIdentity.Demo/Dockerfile, which builds from the repository root. +# Context for Csag.WorkloadIdentity.Demo/Dockerfile, which builds from the repository root. **/bin/ **/obj/ **/TestResults/ @@ -17,4 +17,4 @@ package-lock.json **/*.user **/*.md # The library's csproj packs its README into the NuGet package. -!Csag.AzureSqlFederatedIdentity/README.md +!Csag.WorkloadIdentity/README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ede7ec..9f0a6f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,7 @@ jobs: retention-days: 7 - name: Pack (smoke test) - run: dotnet pack Csag.AzureSqlFederatedIdentity --configuration Release --no-build -o ./nupkgs + run: dotnet pack Csag.WorkloadIdentity --configuration Release --no-build -o ./nupkgs - name: Upload packages uses: actions/upload-artifact@v7 @@ -88,15 +88,15 @@ jobs: uses: actions/checkout@v7.0.1 - name: Build Demo image - run: docker build -f Csag.AzureSqlFederatedIdentity.Demo/Dockerfile -t csag-demo . + 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. - name: Smoke test Demo image run: | docker run -d -p 8080:8080 --name csag-demo \ - -e Csag.AzureSqlFederatedIdentity__TenantId=placeholder \ - -e Csag.AzureSqlFederatedIdentity__ClientId=placeholder \ - -e Csag.AzureSqlFederatedIdentity__Google__ServiceAccountEmail=placeholder@example.invalid \ + -e Csag.WorkloadIdentity__TenantId=placeholder \ + -e Csag.WorkloadIdentity__ClientId=placeholder \ + -e Csag.WorkloadIdentity__Google__ServiceAccountEmail=placeholder@example.invalid \ csag-demo curl --fail --silent --show-error --retry 10 --retry-connrefused --retry-all-errors --retry-delay 1 http://localhost:8080/ test "$(docker exec csag-demo id -u)" != "0" diff --git a/.github/workflows/dependabot-changeset.yml b/.github/workflows/dependabot-changeset.yml index 06f2844..a5090d7 100644 --- a/.github/workflows/dependabot-changeset.yml +++ b/.github/workflows/dependabot-changeset.yml @@ -47,7 +47,7 @@ jobs: # dependency graph (packages.lock.json) change. Any Dependabot update # that touches none of these — root npm dev-deps, GitHub Actions, other # tooling — ships no code and gets an empty changeset instead. - CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- 'Csag.AzureSqlFederatedIdentity/*.csproj' 'Csag.AzureSqlFederatedIdentity/packages.lock.json' 'Directory.Packages.props') + CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- 'Csag.WorkloadIdentity/*.csproj' 'Csag.WorkloadIdentity/packages.lock.json' 'Directory.Packages.props') if [ -n "$CHANGED" ]; then echo "changed=true" >> "$GITHUB_OUTPUT" echo "Package-affecting files changed:" diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml index 864ad3f..0803214 100644 --- a/.github/workflows/nuget-publish.yml +++ b/.github/workflows/nuget-publish.yml @@ -30,7 +30,7 @@ jobs: - name: Extract version from tag id: version run: | - # Tag format: @neolution-ch/csag-azure-sql-federated-identity@0.1.0 + # Tag format: @neolution-ch/csag-workload-identity@0.1.0 TAG="${{ github.event.release.tag_name }}" echo "version=${TAG##*@}" >> "$GITHUB_OUTPUT" @@ -49,12 +49,12 @@ jobs: run: dotnet test --configuration Release --no-build - name: Pack - run: dotnet pack Csag.AzureSqlFederatedIdentity --configuration Release --no-build -p:Version=${{ steps.version.outputs.version }} -o ./nupkgs + run: dotnet pack Csag.WorkloadIdentity --configuration Release --no-build -p:Version=${{ steps.version.outputs.version }} -o ./nupkgs - name: Verify packed version matches the release tag run: | ls ./nupkgs - test -f "./nupkgs/Csag.AzureSqlFederatedIdentity.${{ steps.version.outputs.version }}.nupkg" + test -f "./nupkgs/Csag.WorkloadIdentity.${{ steps.version.outputs.version }}.nupkg" - name: Push to nuget.org run: | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 78bcb54..1439d20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,12 @@ # Contributing -Thank you for helping improve Csag.AzureSqlFederatedIdentity. Bug reports, questions and pull requests are welcome on [GitHub](https://github.com/neolution-ch/Neolution.AzureSqlFederatedIdentity). For vulnerabilities, please follow [SECURITY.md](./SECURITY.md) instead of opening an issue. +Thank you for helping improve Csag.WorkloadIdentity. Bug reports, questions and pull requests are welcome on [GitHub](https://github.com/neolution-ch/Neolution.AzureSqlFederatedIdentity). For vulnerabilities, please follow [SECURITY.md](./SECURITY.md) instead of opening an issue. ## Prerequisites - The .NET SDK pinned in [global.json](./global.json) (10.0.4xx; a newer 10.0 minor rolls forward), plus the **.NET 8 runtime**, because the tests also run on `net8.0`. `dotnet --list-runtimes` should list `Microsoft.NETCore.App 8.0.x` alongside 10.0.x. - [Node.js](https://nodejs.org/) 24 for the changesets tooling: run `npm ci` once in the repository root. -- Docker, only if you want to build the Demo container (see the [Demo README](./Csag.AzureSqlFederatedIdentity.Demo/README.md)). +- Docker, only if you want to build the Demo container (see the [Demo README](./Csag.WorkloadIdentity.Demo/README.md)). ## Build and test @@ -15,7 +15,7 @@ dotnet restore --locked-mode # what CI runs; fails if a packages.lock.jso dotnet build -c Release # warnings are errors in Release, so this is the gate to pass dotnet test -c Release # runs the suite on net8.0 and net10.0 dotnet test -c Release -f net10.0 # a single target framework, for a quicker loop -dotnet pack Csag.AzureSqlFederatedIdentity -c Release -o ./nupkgs +dotnet pack Csag.WorkloadIdentity -c Release -o ./nupkgs ``` The library targets `net8.0` and `net10.0`, and CI runs the tests on both; a change is not done until both are green. Build in `Release` before you push: `TreatWarningsAsErrors` is on and the StyleCop rules from `Neolution.CodeAnalysis` are enforced there, so a build that is clean in `Debug` can still fail. Fix every warning rather than suppressing it. @@ -29,7 +29,7 @@ The analyzers enforce most of these; the rest come from the existing code. - `using` directives inside the namespace, sorted alphabetically with `System` namespaces first. - Nullable reference types are enabled; `ConfigureAwait(false)` on every `await` in the library. - Comments explain the non-obvious *why* of the code as it stands; they do not narrate edits or previous states. -- Tests use xunit, Shouldly and NSubstitute, follow the `Given__When__Then_` naming with Arrange/Act/Assert sections, and live in `Csag.AzureSqlFederatedIdentity.UnitTests`. Read an existing test class before adding one. +- Tests use xunit, Shouldly and NSubstitute, follow the `Given__When__Then_` naming with Arrange/Act/Assert sections, and live in `Csag.WorkloadIdentity.UnitTests`. Read an existing test class before adding one. ## Dependencies @@ -54,7 +54,7 @@ A changeset file looks like this: ```markdown --- -"@neolution-ch/csag-azure-sql-federated-identity": patch +"@neolution-ch/csag-workload-identity": patch --- Describe the change from the consumer's point of view. diff --git a/Csag.AzureSqlFederatedIdentity.Demo/Dockerfile b/Csag.AzureSqlFederatedIdentity.Demo/Dockerfile deleted file mode 100644 index 28d0b76..0000000 --- a/Csag.AzureSqlFederatedIdentity.Demo/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Build context is the repository root: -# docker build -f Csag.AzureSqlFederatedIdentity.Demo/Dockerfile -t csag-demo . -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build -WORKDIR /src - -# Project and lock files first, so the restore layer is reused until a dependency changes. -COPY global.json nuget.config Directory.Build.props Directory.Packages.props ./ -COPY Csag.AzureSqlFederatedIdentity/Csag.AzureSqlFederatedIdentity.csproj Csag.AzureSqlFederatedIdentity/packages.lock.json Csag.AzureSqlFederatedIdentity/ -COPY Csag.AzureSqlFederatedIdentity.Demo/Csag.AzureSqlFederatedIdentity.Demo.csproj Csag.AzureSqlFederatedIdentity.Demo/packages.lock.json Csag.AzureSqlFederatedIdentity.Demo/ -RUN dotnet restore Csag.AzureSqlFederatedIdentity.Demo --locked-mode - -COPY . . -RUN dotnet publish Csag.AzureSqlFederatedIdentity.Demo -c Release --no-restore -o /app/publish - -FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime -WORKDIR /app - -# Cloud Run sends requests to the container on $PORT, which defaults to 8080. -ENV ASPNETCORE_HTTP_PORTS=8080 -EXPOSE 8080 - -# Non-root user shipped with the aspnet image. -USER app - -COPY --from=build /app/publish . -ENTRYPOINT ["dotnet", "Csag.AzureSqlFederatedIdentity.Demo.dll"] diff --git a/Csag.AzureSqlFederatedIdentity/package.json b/Csag.AzureSqlFederatedIdentity/package.json deleted file mode 100644 index 85ad981..0000000 --- a/Csag.AzureSqlFederatedIdentity/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@neolution-ch/csag-azure-sql-federated-identity", - "version": "0.0.1", - "private": true -} diff --git a/Csag.AzureSqlFederatedIdentity.Demo/Csag.AzureSqlFederatedIdentity.Demo.csproj b/Csag.WorkloadIdentity.Demo/Csag.WorkloadIdentity.Demo.csproj similarity index 80% rename from Csag.AzureSqlFederatedIdentity.Demo/Csag.AzureSqlFederatedIdentity.Demo.csproj rename to Csag.WorkloadIdentity.Demo/Csag.WorkloadIdentity.Demo.csproj index ae7fa75..423a0b7 100644 --- a/Csag.AzureSqlFederatedIdentity.Demo/Csag.AzureSqlFederatedIdentity.Demo.csproj +++ b/Csag.WorkloadIdentity.Demo/Csag.WorkloadIdentity.Demo.csproj @@ -12,7 +12,7 @@ - + diff --git a/Csag.AzureSqlFederatedIdentity.Demo/Database/AppDbContext.cs b/Csag.WorkloadIdentity.Demo/Database/AppDbContext.cs similarity index 84% rename from Csag.AzureSqlFederatedIdentity.Demo/Database/AppDbContext.cs rename to Csag.WorkloadIdentity.Demo/Database/AppDbContext.cs index caaa49b..5070205 100644 --- a/Csag.AzureSqlFederatedIdentity.Demo/Database/AppDbContext.cs +++ b/Csag.WorkloadIdentity.Demo/Database/AppDbContext.cs @@ -1,6 +1,6 @@ -namespace Csag.AzureSqlFederatedIdentity.Demo.Database +namespace Csag.WorkloadIdentity.Demo.Database { - using Csag.AzureSqlFederatedIdentity.Demo.Entities; + using Csag.WorkloadIdentity.Demo.Entities; using Microsoft.EntityFrameworkCore; public class AppDbContext(DbContextOptions options) : DbContext(options) diff --git a/Csag.AzureSqlFederatedIdentity.Demo/Database/AppDbContextFactory.cs b/Csag.WorkloadIdentity.Demo/Database/AppDbContextFactory.cs similarity index 93% rename from Csag.AzureSqlFederatedIdentity.Demo/Database/AppDbContextFactory.cs rename to Csag.WorkloadIdentity.Demo/Database/AppDbContextFactory.cs index d687cc0..6b7d731 100644 --- a/Csag.AzureSqlFederatedIdentity.Demo/Database/AppDbContextFactory.cs +++ b/Csag.WorkloadIdentity.Demo/Database/AppDbContextFactory.cs @@ -1,7 +1,7 @@ -namespace Csag.AzureSqlFederatedIdentity.Demo.Database +namespace Csag.WorkloadIdentity.Demo.Database { using System; - using Csag.AzureSqlFederatedIdentity.Abstractions; + using Csag.WorkloadIdentity.Abstractions; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; diff --git a/Csag.AzureSqlFederatedIdentity.Demo/Database/IAppDbContextFactory.cs b/Csag.WorkloadIdentity.Demo/Database/IAppDbContextFactory.cs similarity index 73% rename from Csag.AzureSqlFederatedIdentity.Demo/Database/IAppDbContextFactory.cs rename to Csag.WorkloadIdentity.Demo/Database/IAppDbContextFactory.cs index 814c987..c648ae2 100644 --- a/Csag.AzureSqlFederatedIdentity.Demo/Database/IAppDbContextFactory.cs +++ b/Csag.WorkloadIdentity.Demo/Database/IAppDbContextFactory.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Demo.Database +namespace Csag.WorkloadIdentity.Demo.Database { public interface IAppDbContextFactory { diff --git a/Csag.WorkloadIdentity.Demo/Dockerfile b/Csag.WorkloadIdentity.Demo/Dockerfile new file mode 100644 index 0000000..1aeeee5 --- /dev/null +++ b/Csag.WorkloadIdentity.Demo/Dockerfile @@ -0,0 +1,26 @@ +# Build context is the repository root: +# docker build -f Csag.WorkloadIdentity.Demo/Dockerfile -t csag-demo . +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src + +# Project and lock files first, so the restore layer is reused until a dependency changes. +COPY global.json nuget.config Directory.Build.props Directory.Packages.props ./ +COPY Csag.WorkloadIdentity/Csag.WorkloadIdentity.csproj Csag.WorkloadIdentity/packages.lock.json Csag.WorkloadIdentity/ +COPY Csag.WorkloadIdentity.Demo/Csag.WorkloadIdentity.Demo.csproj Csag.WorkloadIdentity.Demo/packages.lock.json Csag.WorkloadIdentity.Demo/ +RUN dotnet restore Csag.WorkloadIdentity.Demo --locked-mode + +COPY . . +RUN dotnet publish Csag.WorkloadIdentity.Demo -c Release --no-restore -o /app/publish + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime +WORKDIR /app + +# Cloud Run sends requests to the container on $PORT, which defaults to 8080. +ENV ASPNETCORE_HTTP_PORTS=8080 +EXPOSE 8080 + +# Non-root user shipped with the aspnet image. +USER app + +COPY --from=build /app/publish . +ENTRYPOINT ["dotnet", "Csag.WorkloadIdentity.Demo.dll"] diff --git a/Csag.AzureSqlFederatedIdentity.Demo/Entities/TestEntity.cs b/Csag.WorkloadIdentity.Demo/Entities/TestEntity.cs similarity index 70% rename from Csag.AzureSqlFederatedIdentity.Demo/Entities/TestEntity.cs rename to Csag.WorkloadIdentity.Demo/Entities/TestEntity.cs index e763440..f07df17 100644 --- a/Csag.AzureSqlFederatedIdentity.Demo/Entities/TestEntity.cs +++ b/Csag.WorkloadIdentity.Demo/Entities/TestEntity.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Demo.Entities +namespace Csag.WorkloadIdentity.Demo.Entities { public class TestEntity { diff --git a/Csag.AzureSqlFederatedIdentity.Demo/Program.cs b/Csag.WorkloadIdentity.Demo/Program.cs similarity index 94% rename from Csag.AzureSqlFederatedIdentity.Demo/Program.cs rename to Csag.WorkloadIdentity.Demo/Program.cs index 664766a..428ec5c 100644 --- a/Csag.AzureSqlFederatedIdentity.Demo/Program.cs +++ b/Csag.WorkloadIdentity.Demo/Program.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -using Csag.AzureSqlFederatedIdentity; -using Csag.AzureSqlFederatedIdentity.Demo.Database; +using Csag.WorkloadIdentity; +using Csag.WorkloadIdentity.Demo.Database; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); diff --git a/Csag.AzureSqlFederatedIdentity.Demo/Properties/launchSettings.json b/Csag.WorkloadIdentity.Demo/Properties/launchSettings.json similarity index 100% rename from Csag.AzureSqlFederatedIdentity.Demo/Properties/launchSettings.json rename to Csag.WorkloadIdentity.Demo/Properties/launchSettings.json diff --git a/Csag.AzureSqlFederatedIdentity.Demo/README.md b/Csag.WorkloadIdentity.Demo/README.md similarity index 74% rename from Csag.AzureSqlFederatedIdentity.Demo/README.md rename to Csag.WorkloadIdentity.Demo/README.md index 03ce0a9..e2a4fa8 100644 --- a/Csag.AzureSqlFederatedIdentity.Demo/README.md +++ b/Csag.WorkloadIdentity.Demo/README.md @@ -1,6 +1,6 @@ -# Csag.AzureSqlFederatedIdentity.Demo +# 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.AzureSqlFederatedIdentity` 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 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`. | Route | Behaviour | |---|---| @@ -21,18 +21,18 @@ A minimal ASP.NET Core app that runs on Google Cloud Run and reads from Azure SQ | Setting | User-secrets / JSON key | Environment variable | |---|---|---| -| Azure AD tenant ID | `Csag.AzureSqlFederatedIdentity:TenantId` | `Csag.AzureSqlFederatedIdentity__TenantId` | -| Azure AD application (client) ID | `Csag.AzureSqlFederatedIdentity:ClientId` | `Csag.AzureSqlFederatedIdentity__ClientId` | -| Google service account email | `Csag.AzureSqlFederatedIdentity:Google:ServiceAccountEmail` | `Csag.AzureSqlFederatedIdentity__Google__ServiceAccountEmail` | +| 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` | | 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`. ```shell -dotnet user-secrets set "Csag.AzureSqlFederatedIdentity:TenantId" "" --project Csag.AzureSqlFederatedIdentity.Demo -dotnet user-secrets set "Csag.AzureSqlFederatedIdentity:ClientId" "" --project Csag.AzureSqlFederatedIdentity.Demo -dotnet user-secrets set "Csag.AzureSqlFederatedIdentity:Google:ServiceAccountEmail" "@.iam.gserviceaccount.com" --project Csag.AzureSqlFederatedIdentity.Demo -dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=tcp:.database.windows.net,1433;Initial Catalog=;Encrypt=True" --project Csag.AzureSqlFederatedIdentity.Demo +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 "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. @@ -54,7 +54,7 @@ INSERT INTO dbo.TestTable (Value) VALUES (N'hello'), (N'world'); ## Run from source ```shell -dotnet run --project Csag.AzureSqlFederatedIdentity.Demo +dotnet run --project Csag.WorkloadIdentity.Demo ``` The application stays in the foreground; request the endpoint from a second terminal: @@ -68,16 +68,16 @@ curl http://localhost:5172/test The image is built from the **repository root**, because the Demo references the library project: ```shell -docker build -f Csag.AzureSqlFederatedIdentity.Demo/Dockerfile -t csag-demo . +docker build -f Csag.WorkloadIdentity.Demo/Dockerfile -t csag-demo . ``` The container listens on port 8080 and runs as the non-root `app` user (uid 1654). Outside Cloud Run it has no ADC of its own, so mount your workstation's credential file and point the Google SDK at it. `gcloud` creates that file readable by your user only, so for this local test run the container as your own user (`--user`), which overrides the image's `app` user; on Windows the mounted file is readable regardless and `--user` can be left out. Cloud Run needs none of this: ```shell docker run --rm -p 8080:8080 --user "$(id -u):$(id -g)" \ - -e Csag.AzureSqlFederatedIdentity__TenantId="" \ - -e Csag.AzureSqlFederatedIdentity__ClientId="" \ - -e Csag.AzureSqlFederatedIdentity__Google__ServiceAccountEmail="@.iam.gserviceaccount.com" \ + -e Csag.WorkloadIdentity__TenantId="" \ + -e Csag.WorkloadIdentity__ClientId="" \ + -e Csag.WorkloadIdentity__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 \ diff --git a/Csag.AzureSqlFederatedIdentity.Demo/appsettings.Development.json b/Csag.WorkloadIdentity.Demo/appsettings.Development.json similarity index 100% rename from Csag.AzureSqlFederatedIdentity.Demo/appsettings.Development.json rename to Csag.WorkloadIdentity.Demo/appsettings.Development.json diff --git a/Csag.AzureSqlFederatedIdentity.Demo/appsettings.json b/Csag.WorkloadIdentity.Demo/appsettings.json similarity index 88% rename from Csag.AzureSqlFederatedIdentity.Demo/appsettings.json rename to Csag.WorkloadIdentity.Demo/appsettings.json index 4bfd752..a9d7c15 100644 --- a/Csag.AzureSqlFederatedIdentity.Demo/appsettings.json +++ b/Csag.WorkloadIdentity.Demo/appsettings.json @@ -6,7 +6,7 @@ } }, "AllowedHosts": "*", - "Csag.AzureSqlFederatedIdentity": { + "Csag.WorkloadIdentity": { "TenantId": "", "ClientId": "", "Google": { diff --git a/Csag.AzureSqlFederatedIdentity.Demo/packages.lock.json b/Csag.WorkloadIdentity.Demo/packages.lock.json similarity index 99% rename from Csag.AzureSqlFederatedIdentity.Demo/packages.lock.json rename to Csag.WorkloadIdentity.Demo/packages.lock.json index 9f8f1aa..92f975f 100644 --- a/Csag.AzureSqlFederatedIdentity.Demo/packages.lock.json +++ b/Csag.WorkloadIdentity.Demo/packages.lock.json @@ -296,7 +296,7 @@ "resolved": "9.0.13", "contentHash": "t8S9IDpjJKsLpLkeBdW8cWtcPyYqrGu93Dej1RO6WwuL/lkFSqWlan3rMJfortqz1mRIh+sys2AFsSA6jWJ3Jg==" }, - "csag.azuresqlfederatedidentity": { + "csag.workloadidentity": { "type": "Project", "dependencies": { "Azure.Core": "[1.53.0, )", diff --git a/Csag.AzureSqlFederatedIdentity.Demo/wwwroot/favicon.ico b/Csag.WorkloadIdentity.Demo/wwwroot/favicon.ico similarity index 100% rename from Csag.AzureSqlFederatedIdentity.Demo/wwwroot/favicon.ico rename to Csag.WorkloadIdentity.Demo/wwwroot/favicon.ico diff --git a/Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlFederatedIdentityOptionsValidatorTests.cs b/Csag.WorkloadIdentity.UnitTests/AzureSqlFederatedIdentityOptionsValidatorTests.cs similarity index 97% rename from Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlFederatedIdentityOptionsValidatorTests.cs rename to Csag.WorkloadIdentity.UnitTests/AzureSqlFederatedIdentityOptionsValidatorTests.cs index 026548a..78088a1 100644 --- a/Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlFederatedIdentityOptionsValidatorTests.cs +++ b/Csag.WorkloadIdentity.UnitTests/AzureSqlFederatedIdentityOptionsValidatorTests.cs @@ -1,6 +1,6 @@ -namespace Csag.AzureSqlFederatedIdentity.UnitTests +namespace Csag.WorkloadIdentity.UnitTests { - using Csag.AzureSqlFederatedIdentity.Options; + using Csag.WorkloadIdentity.Options; using Shouldly; /// diff --git a/Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlTokenExchangerTests.cs b/Csag.WorkloadIdentity.UnitTests/AzureSqlTokenExchangerTests.cs similarity index 97% rename from Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlTokenExchangerTests.cs rename to Csag.WorkloadIdentity.UnitTests/AzureSqlTokenExchangerTests.cs index dd95c5b..41ed033 100644 --- a/Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlTokenExchangerTests.cs +++ b/Csag.WorkloadIdentity.UnitTests/AzureSqlTokenExchangerTests.cs @@ -1,8 +1,8 @@ -namespace Csag.AzureSqlFederatedIdentity.UnitTests +namespace Csag.WorkloadIdentity.UnitTests { using Azure.Core; - using Csag.AzureSqlFederatedIdentity.Abstractions; - using Csag.AzureSqlFederatedIdentity.Internal; + using Csag.WorkloadIdentity.Abstractions; + using Csag.WorkloadIdentity.Internal; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using Shouldly; diff --git a/Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlTokenProviderTests.cs b/Csag.WorkloadIdentity.UnitTests/AzureSqlTokenProviderTests.cs similarity index 98% rename from Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlTokenProviderTests.cs rename to Csag.WorkloadIdentity.UnitTests/AzureSqlTokenProviderTests.cs index 61e6d62..5c06e12 100644 --- a/Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlTokenProviderTests.cs +++ b/Csag.WorkloadIdentity.UnitTests/AzureSqlTokenProviderTests.cs @@ -1,9 +1,9 @@ -namespace Csag.AzureSqlFederatedIdentity.UnitTests +namespace Csag.WorkloadIdentity.UnitTests { using Azure.Core; - using Csag.AzureSqlFederatedIdentity.Abstractions; - using Csag.AzureSqlFederatedIdentity.Internal; - using Csag.AzureSqlFederatedIdentity.Options; + using Csag.WorkloadIdentity.Abstractions; + using Csag.WorkloadIdentity.Internal; + using Csag.WorkloadIdentity.Options; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Extensions.Time.Testing; diff --git a/Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlTokenRefreshServiceTests.cs b/Csag.WorkloadIdentity.UnitTests/AzureSqlTokenRefreshServiceTests.cs similarity index 98% rename from Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlTokenRefreshServiceTests.cs rename to Csag.WorkloadIdentity.UnitTests/AzureSqlTokenRefreshServiceTests.cs index 412a32b..a083166 100644 --- a/Csag.AzureSqlFederatedIdentity.UnitTests/AzureSqlTokenRefreshServiceTests.cs +++ b/Csag.WorkloadIdentity.UnitTests/AzureSqlTokenRefreshServiceTests.cs @@ -1,9 +1,9 @@ -namespace Csag.AzureSqlFederatedIdentity.UnitTests +namespace Csag.WorkloadIdentity.UnitTests { - using Csag.AzureSqlFederatedIdentity.Abstractions; - using Csag.AzureSqlFederatedIdentity.Internal; - using Csag.AzureSqlFederatedIdentity.Internal.Services; - using Csag.AzureSqlFederatedIdentity.Options; + using Csag.WorkloadIdentity.Abstractions; + using Csag.WorkloadIdentity.Internal; + using Csag.WorkloadIdentity.Internal.Services; + using Csag.WorkloadIdentity.Options; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Extensions.Time.Testing; diff --git a/Csag.AzureSqlFederatedIdentity.UnitTests/Csag.AzureSqlFederatedIdentity.UnitTests.csproj b/Csag.WorkloadIdentity.UnitTests/Csag.WorkloadIdentity.UnitTests.csproj similarity index 93% rename from Csag.AzureSqlFederatedIdentity.UnitTests/Csag.AzureSqlFederatedIdentity.UnitTests.csproj rename to Csag.WorkloadIdentity.UnitTests/Csag.WorkloadIdentity.UnitTests.csproj index d0e8ad6..56fca07 100644 --- a/Csag.AzureSqlFederatedIdentity.UnitTests/Csag.AzureSqlFederatedIdentity.UnitTests.csproj +++ b/Csag.WorkloadIdentity.UnitTests/Csag.WorkloadIdentity.UnitTests.csproj @@ -31,7 +31,7 @@ - + diff --git a/Csag.AzureSqlFederatedIdentity.UnitTests/FederatedIdentityServiceCollectionExtensionsTests.cs b/Csag.WorkloadIdentity.UnitTests/FederatedIdentityServiceCollectionExtensionsTests.cs similarity index 97% rename from Csag.AzureSqlFederatedIdentity.UnitTests/FederatedIdentityServiceCollectionExtensionsTests.cs rename to Csag.WorkloadIdentity.UnitTests/FederatedIdentityServiceCollectionExtensionsTests.cs index 79847d8..2058699 100644 --- a/Csag.AzureSqlFederatedIdentity.UnitTests/FederatedIdentityServiceCollectionExtensionsTests.cs +++ b/Csag.WorkloadIdentity.UnitTests/FederatedIdentityServiceCollectionExtensionsTests.cs @@ -1,8 +1,8 @@ -namespace Csag.AzureSqlFederatedIdentity.UnitTests +namespace Csag.WorkloadIdentity.UnitTests { - using Csag.AzureSqlFederatedIdentity.Abstractions; - using Csag.AzureSqlFederatedIdentity.Internal.Services; - using Csag.AzureSqlFederatedIdentity.Options; + using Csag.WorkloadIdentity.Abstractions; + using Csag.WorkloadIdentity.Internal.Services; + using Csag.WorkloadIdentity.Options; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/Csag.AzureSqlFederatedIdentity.UnitTests/GoogleIdTokenProviderTests.cs b/Csag.WorkloadIdentity.UnitTests/GoogleIdTokenProviderTests.cs similarity index 98% rename from Csag.AzureSqlFederatedIdentity.UnitTests/GoogleIdTokenProviderTests.cs rename to Csag.WorkloadIdentity.UnitTests/GoogleIdTokenProviderTests.cs index 84dcd0e..7181d9c 100644 --- a/Csag.AzureSqlFederatedIdentity.UnitTests/GoogleIdTokenProviderTests.cs +++ b/Csag.WorkloadIdentity.UnitTests/GoogleIdTokenProviderTests.cs @@ -1,7 +1,7 @@ -namespace Csag.AzureSqlFederatedIdentity.UnitTests +namespace Csag.WorkloadIdentity.UnitTests { - using Csag.AzureSqlFederatedIdentity.Internal; - using Csag.AzureSqlFederatedIdentity.Options; + using Csag.WorkloadIdentity.Internal; + using Csag.WorkloadIdentity.Options; using Google.Cloud.Iam.Credentials.V1; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; diff --git a/Csag.AzureSqlFederatedIdentity.UnitTests/packages.lock.json b/Csag.WorkloadIdentity.UnitTests/packages.lock.json similarity index 99% rename from Csag.AzureSqlFederatedIdentity.UnitTests/packages.lock.json rename to Csag.WorkloadIdentity.UnitTests/packages.lock.json index 81971fa..5ea2442 100644 --- a/Csag.AzureSqlFederatedIdentity.UnitTests/packages.lock.json +++ b/Csag.WorkloadIdentity.UnitTests/packages.lock.json @@ -548,7 +548,7 @@ "xunit.extensibility.core": "[2.9.3]" } }, - "csag.azuresqlfederatedidentity": { + "csag.workloadidentity": { "type": "Project", "dependencies": { "Azure.Core": "[1.53.0, )", @@ -1243,7 +1243,7 @@ "xunit.extensibility.core": "[2.9.3]" } }, - "csag.azuresqlfederatedidentity": { + "csag.workloadidentity": { "type": "Project", "dependencies": { "Azure.Core": "[1.53.0, )", diff --git a/Csag.AzureSqlFederatedIdentity.sln b/Csag.WorkloadIdentity.sln similarity index 71% rename from Csag.AzureSqlFederatedIdentity.sln rename to Csag.WorkloadIdentity.sln index db1e125..10cec41 100644 --- a/Csag.AzureSqlFederatedIdentity.sln +++ b/Csag.WorkloadIdentity.sln @@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.13.36105.23 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csag.AzureSqlFederatedIdentity", "Csag.AzureSqlFederatedIdentity\Csag.AzureSqlFederatedIdentity.csproj", "{365E1220-70DE-41AA-8434-F665AE78CC71}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csag.WorkloadIdentity", "Csag.WorkloadIdentity\Csag.WorkloadIdentity.csproj", "{365E1220-70DE-41AA-8434-F665AE78CC71}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csag.AzureSqlFederatedIdentity.UnitTests", "Csag.AzureSqlFederatedIdentity.UnitTests\Csag.AzureSqlFederatedIdentity.UnitTests.csproj", "{DCCFAA71-81F1-44D2-8687-8BAF2C55D8BB}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csag.WorkloadIdentity.UnitTests", "Csag.WorkloadIdentity.UnitTests\Csag.WorkloadIdentity.UnitTests.csproj", "{DCCFAA71-81F1-44D2-8687-8BAF2C55D8BB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csag.AzureSqlFederatedIdentity.Demo", "Csag.AzureSqlFederatedIdentity.Demo\Csag.AzureSqlFederatedIdentity.Demo.csproj", "{1F807A58-8D24-5EDB-D0DD-4D0E9F5B3920}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csag.WorkloadIdentity.Demo", "Csag.WorkloadIdentity.Demo\Csag.WorkloadIdentity.Demo.csproj", "{1F807A58-8D24-5EDB-D0DD-4D0E9F5B3920}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Csag.AzureSqlFederatedIdentity/Abstractions/IAzureSqlTokenExchanger.cs b/Csag.WorkloadIdentity/Abstractions/IAzureSqlTokenExchanger.cs similarity index 93% rename from Csag.AzureSqlFederatedIdentity/Abstractions/IAzureSqlTokenExchanger.cs rename to Csag.WorkloadIdentity/Abstractions/IAzureSqlTokenExchanger.cs index c152764..2d1a512 100644 --- a/Csag.AzureSqlFederatedIdentity/Abstractions/IAzureSqlTokenExchanger.cs +++ b/Csag.WorkloadIdentity/Abstractions/IAzureSqlTokenExchanger.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Abstractions +namespace Csag.WorkloadIdentity.Abstractions { using Azure.Core; diff --git a/Csag.AzureSqlFederatedIdentity/Abstractions/IAzureSqlTokenProvider.cs b/Csag.WorkloadIdentity/Abstractions/IAzureSqlTokenProvider.cs similarity index 90% rename from Csag.AzureSqlFederatedIdentity/Abstractions/IAzureSqlTokenProvider.cs rename to Csag.WorkloadIdentity/Abstractions/IAzureSqlTokenProvider.cs index 12fe108..a765360 100644 --- a/Csag.AzureSqlFederatedIdentity/Abstractions/IAzureSqlTokenProvider.cs +++ b/Csag.WorkloadIdentity/Abstractions/IAzureSqlTokenProvider.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Abstractions +namespace Csag.WorkloadIdentity.Abstractions { using System.Threading; using System.Threading.Tasks; diff --git a/Csag.AzureSqlFederatedIdentity/Abstractions/IGoogleIdTokenProvider.cs b/Csag.WorkloadIdentity/Abstractions/IGoogleIdTokenProvider.cs similarity index 92% rename from Csag.AzureSqlFederatedIdentity/Abstractions/IGoogleIdTokenProvider.cs rename to Csag.WorkloadIdentity/Abstractions/IGoogleIdTokenProvider.cs index f7c84f4..244a429 100644 --- a/Csag.AzureSqlFederatedIdentity/Abstractions/IGoogleIdTokenProvider.cs +++ b/Csag.WorkloadIdentity/Abstractions/IGoogleIdTokenProvider.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Abstractions +namespace Csag.WorkloadIdentity.Abstractions { /// /// Provides a method to obtain a Google-signed ID token for the target service account. diff --git a/Csag.AzureSqlFederatedIdentity/AzureSqlTokenProvider.cs b/Csag.WorkloadIdentity/AzureSqlTokenProvider.cs similarity index 97% rename from Csag.AzureSqlFederatedIdentity/AzureSqlTokenProvider.cs rename to Csag.WorkloadIdentity/AzureSqlTokenProvider.cs index addbc0f..99c1400 100644 --- a/Csag.AzureSqlFederatedIdentity/AzureSqlTokenProvider.cs +++ b/Csag.WorkloadIdentity/AzureSqlTokenProvider.cs @@ -1,8 +1,8 @@ -namespace Csag.AzureSqlFederatedIdentity +namespace Csag.WorkloadIdentity { - using Csag.AzureSqlFederatedIdentity.Abstractions; - using Csag.AzureSqlFederatedIdentity.Internal; - using Csag.AzureSqlFederatedIdentity.Options; + using Csag.WorkloadIdentity.Abstractions; + using Csag.WorkloadIdentity.Internal; + using Csag.WorkloadIdentity.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; diff --git a/Csag.AzureSqlFederatedIdentity/CHANGELOG.md b/Csag.WorkloadIdentity/CHANGELOG.md similarity index 87% rename from Csag.AzureSqlFederatedIdentity/CHANGELOG.md rename to Csag.WorkloadIdentity/CHANGELOG.md index 6f1d471..5ef1bd1 100644 --- a/Csag.AzureSqlFederatedIdentity/CHANGELOG.md +++ b/Csag.WorkloadIdentity/CHANGELOG.md @@ -1,4 +1,4 @@ -# @neolution-ch/csag-azure-sql-federated-identity +# @neolution-ch/csag-workload-identity ## 0.0.1 diff --git a/Csag.AzureSqlFederatedIdentity/Csag.AzureSqlFederatedIdentity.csproj b/Csag.WorkloadIdentity/Csag.WorkloadIdentity.csproj similarity index 95% rename from Csag.AzureSqlFederatedIdentity/Csag.AzureSqlFederatedIdentity.csproj rename to Csag.WorkloadIdentity/Csag.WorkloadIdentity.csproj index 6e69d7c..ac5301d 100644 --- a/Csag.AzureSqlFederatedIdentity/Csag.AzureSqlFederatedIdentity.csproj +++ b/Csag.WorkloadIdentity/Csag.WorkloadIdentity.csproj @@ -25,7 +25,7 @@ - + diff --git a/Csag.AzureSqlFederatedIdentity/FederatedIdentityServiceCollectionExtensions.cs b/Csag.WorkloadIdentity/FederatedIdentityServiceCollectionExtensions.cs similarity index 95% rename from Csag.AzureSqlFederatedIdentity/FederatedIdentityServiceCollectionExtensions.cs rename to Csag.WorkloadIdentity/FederatedIdentityServiceCollectionExtensions.cs index 96b79e4..4c3f943 100644 --- a/Csag.AzureSqlFederatedIdentity/FederatedIdentityServiceCollectionExtensions.cs +++ b/Csag.WorkloadIdentity/FederatedIdentityServiceCollectionExtensions.cs @@ -1,9 +1,9 @@ -namespace Csag.AzureSqlFederatedIdentity +namespace Csag.WorkloadIdentity { - using Csag.AzureSqlFederatedIdentity.Abstractions; - using Csag.AzureSqlFederatedIdentity.Internal; - using Csag.AzureSqlFederatedIdentity.Internal.Services; - using Csag.AzureSqlFederatedIdentity.Options; + using Csag.WorkloadIdentity.Abstractions; + using Csag.WorkloadIdentity.Internal; + using Csag.WorkloadIdentity.Internal.Services; + using Csag.WorkloadIdentity.Options; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/Csag.AzureSqlFederatedIdentity/Internal/AzureSqlTokenExchanger.cs b/Csag.WorkloadIdentity/Internal/AzureSqlTokenExchanger.cs similarity index 97% rename from Csag.AzureSqlFederatedIdentity/Internal/AzureSqlTokenExchanger.cs rename to Csag.WorkloadIdentity/Internal/AzureSqlTokenExchanger.cs index 91ffbf9..681780c 100644 --- a/Csag.AzureSqlFederatedIdentity/Internal/AzureSqlTokenExchanger.cs +++ b/Csag.WorkloadIdentity/Internal/AzureSqlTokenExchanger.cs @@ -1,7 +1,7 @@ -namespace Csag.AzureSqlFederatedIdentity.Internal +namespace Csag.WorkloadIdentity.Internal { using Azure.Core; - using Csag.AzureSqlFederatedIdentity.Abstractions; + using Csag.WorkloadIdentity.Abstractions; using Microsoft.Extensions.Logging; /// diff --git a/Csag.AzureSqlFederatedIdentity/Internal/ClientAssertionCredentialFactory.cs b/Csag.WorkloadIdentity/Internal/ClientAssertionCredentialFactory.cs similarity index 90% rename from Csag.AzureSqlFederatedIdentity/Internal/ClientAssertionCredentialFactory.cs rename to Csag.WorkloadIdentity/Internal/ClientAssertionCredentialFactory.cs index 183d965..a7bd2f2 100644 --- a/Csag.AzureSqlFederatedIdentity/Internal/ClientAssertionCredentialFactory.cs +++ b/Csag.WorkloadIdentity/Internal/ClientAssertionCredentialFactory.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Internal +namespace Csag.WorkloadIdentity.Internal { using Azure.Core; using Azure.Identity; diff --git a/Csag.AzureSqlFederatedIdentity/Internal/GoogleIdTokenProvider.cs b/Csag.WorkloadIdentity/Internal/GoogleIdTokenProvider.cs similarity index 97% rename from Csag.AzureSqlFederatedIdentity/Internal/GoogleIdTokenProvider.cs rename to Csag.WorkloadIdentity/Internal/GoogleIdTokenProvider.cs index e45e218..b268a75 100644 --- a/Csag.AzureSqlFederatedIdentity/Internal/GoogleIdTokenProvider.cs +++ b/Csag.WorkloadIdentity/Internal/GoogleIdTokenProvider.cs @@ -1,7 +1,7 @@ -namespace Csag.AzureSqlFederatedIdentity.Internal +namespace Csag.WorkloadIdentity.Internal { - using Csag.AzureSqlFederatedIdentity.Abstractions; - using Csag.AzureSqlFederatedIdentity.Options; + using Csag.WorkloadIdentity.Abstractions; + using Csag.WorkloadIdentity.Options; using Google.Cloud.Iam.Credentials.V1; using Grpc.Core; using Microsoft.Extensions.Logging; diff --git a/Csag.AzureSqlFederatedIdentity/Internal/IAzureSqlTokenRefresher.cs b/Csag.WorkloadIdentity/Internal/IAzureSqlTokenRefresher.cs similarity index 93% rename from Csag.AzureSqlFederatedIdentity/Internal/IAzureSqlTokenRefresher.cs rename to Csag.WorkloadIdentity/Internal/IAzureSqlTokenRefresher.cs index 47512bf..5ba9204 100644 --- a/Csag.AzureSqlFederatedIdentity/Internal/IAzureSqlTokenRefresher.cs +++ b/Csag.WorkloadIdentity/Internal/IAzureSqlTokenRefresher.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Internal +namespace Csag.WorkloadIdentity.Internal { /// /// Replaces the currently held Azure SQL access token with a freshly exchanged one, regardless of how much diff --git a/Csag.AzureSqlFederatedIdentity/Internal/IClientAssertionCredentialFactory.cs b/Csag.WorkloadIdentity/Internal/IClientAssertionCredentialFactory.cs similarity index 94% rename from Csag.AzureSqlFederatedIdentity/Internal/IClientAssertionCredentialFactory.cs rename to Csag.WorkloadIdentity/Internal/IClientAssertionCredentialFactory.cs index 7ac5e1d..44ff397 100644 --- a/Csag.AzureSqlFederatedIdentity/Internal/IClientAssertionCredentialFactory.cs +++ b/Csag.WorkloadIdentity/Internal/IClientAssertionCredentialFactory.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Internal +namespace Csag.WorkloadIdentity.Internal { using Azure.Core; diff --git a/Csag.AzureSqlFederatedIdentity/Internal/IIamCredentialsClientFactory.cs b/Csag.WorkloadIdentity/Internal/IIamCredentialsClientFactory.cs similarity index 92% rename from Csag.AzureSqlFederatedIdentity/Internal/IIamCredentialsClientFactory.cs rename to Csag.WorkloadIdentity/Internal/IIamCredentialsClientFactory.cs index 85bfecc..b888482 100644 --- a/Csag.AzureSqlFederatedIdentity/Internal/IIamCredentialsClientFactory.cs +++ b/Csag.WorkloadIdentity/Internal/IIamCredentialsClientFactory.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Internal +namespace Csag.WorkloadIdentity.Internal { using Google.Cloud.Iam.Credentials.V1; diff --git a/Csag.AzureSqlFederatedIdentity/Internal/IamCredentialsClientFactory.cs b/Csag.WorkloadIdentity/Internal/IamCredentialsClientFactory.cs similarity index 90% rename from Csag.AzureSqlFederatedIdentity/Internal/IamCredentialsClientFactory.cs rename to Csag.WorkloadIdentity/Internal/IamCredentialsClientFactory.cs index c7223a0..79cd55e 100644 --- a/Csag.AzureSqlFederatedIdentity/Internal/IamCredentialsClientFactory.cs +++ b/Csag.WorkloadIdentity/Internal/IamCredentialsClientFactory.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Internal +namespace Csag.WorkloadIdentity.Internal { using Google.Cloud.Iam.Credentials.V1; diff --git a/Csag.AzureSqlFederatedIdentity/Internal/Services/AzureSqlTokenRefreshService.cs b/Csag.WorkloadIdentity/Internal/Services/AzureSqlTokenRefreshService.cs similarity index 97% rename from Csag.AzureSqlFederatedIdentity/Internal/Services/AzureSqlTokenRefreshService.cs rename to Csag.WorkloadIdentity/Internal/Services/AzureSqlTokenRefreshService.cs index b212459..6d8fbaa 100644 --- a/Csag.AzureSqlFederatedIdentity/Internal/Services/AzureSqlTokenRefreshService.cs +++ b/Csag.WorkloadIdentity/Internal/Services/AzureSqlTokenRefreshService.cs @@ -1,7 +1,7 @@ -namespace Csag.AzureSqlFederatedIdentity.Internal.Services +namespace Csag.WorkloadIdentity.Internal.Services { - using Csag.AzureSqlFederatedIdentity.Abstractions; - using Csag.AzureSqlFederatedIdentity.Options; + using Csag.WorkloadIdentity.Abstractions; + using Csag.WorkloadIdentity.Options; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; diff --git a/Csag.AzureSqlFederatedIdentity/Options/AzureSqlFederatedIdentityOptions.cs b/Csag.WorkloadIdentity/Options/AzureSqlFederatedIdentityOptions.cs similarity index 95% rename from Csag.AzureSqlFederatedIdentity/Options/AzureSqlFederatedIdentityOptions.cs rename to Csag.WorkloadIdentity/Options/AzureSqlFederatedIdentityOptions.cs index 9d8e39b..a5d479e 100644 --- a/Csag.AzureSqlFederatedIdentity/Options/AzureSqlFederatedIdentityOptions.cs +++ b/Csag.WorkloadIdentity/Options/AzureSqlFederatedIdentityOptions.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Options +namespace Csag.WorkloadIdentity.Options { /// /// Options for configuring Azure SQL federated identity integration. @@ -8,7 +8,7 @@ public class AzureSqlFederatedIdentityOptions /// /// Gets the configuration section the options are bound from by default. /// - public static string ConfigurationSectionName { get; } = "Csag.AzureSqlFederatedIdentity"; + public static string ConfigurationSectionName { get; } = "Csag.WorkloadIdentity"; /// /// Gets or sets the Azure AD tenant ID. diff --git a/Csag.AzureSqlFederatedIdentity/Options/AzureSqlFederatedIdentityOptionsValidator.cs b/Csag.WorkloadIdentity/Options/AzureSqlFederatedIdentityOptionsValidator.cs similarity index 97% rename from Csag.AzureSqlFederatedIdentity/Options/AzureSqlFederatedIdentityOptionsValidator.cs rename to Csag.WorkloadIdentity/Options/AzureSqlFederatedIdentityOptionsValidator.cs index 50ec961..cb91420 100644 --- a/Csag.AzureSqlFederatedIdentity/Options/AzureSqlFederatedIdentityOptionsValidator.cs +++ b/Csag.WorkloadIdentity/Options/AzureSqlFederatedIdentityOptionsValidator.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Options +namespace Csag.WorkloadIdentity.Options { using Microsoft.Extensions.Options; diff --git a/Csag.AzureSqlFederatedIdentity/Options/GoogleOptions.cs b/Csag.WorkloadIdentity/Options/GoogleOptions.cs similarity index 86% rename from Csag.AzureSqlFederatedIdentity/Options/GoogleOptions.cs rename to Csag.WorkloadIdentity/Options/GoogleOptions.cs index c88fcfc..dffcf11 100644 --- a/Csag.AzureSqlFederatedIdentity/Options/GoogleOptions.cs +++ b/Csag.WorkloadIdentity/Options/GoogleOptions.cs @@ -1,4 +1,4 @@ -namespace Csag.AzureSqlFederatedIdentity.Options +namespace Csag.WorkloadIdentity.Options { /// /// Google-specific options for federated identity. diff --git a/Csag.AzureSqlFederatedIdentity/README.md b/Csag.WorkloadIdentity/README.md similarity index 86% rename from Csag.AzureSqlFederatedIdentity/README.md rename to Csag.WorkloadIdentity/README.md index 74bf87c..1827bdf 100644 --- a/Csag.AzureSqlFederatedIdentity/README.md +++ b/Csag.WorkloadIdentity/README.md @@ -1,6 +1,6 @@ -# Csag.AzureSqlFederatedIdentity +# Csag.WorkloadIdentity -[![NuGet](https://img.shields.io/nuget/v/Csag.AzureSqlFederatedIdentity.svg)](https://www.nuget.org/packages/Csag.AzureSqlFederatedIdentity) +[![NuGet](https://img.shields.io/nuget/v/Csag.WorkloadIdentity.svg)](https://www.nuget.org/packages/Csag.WorkloadIdentity) [![License: MIT](https://img.shields.io/badge/License-MIT-lightgray.svg)](https://github.com/neolution-ch/Neolution.AzureSqlFederatedIdentity/blob/main/LICENSE) Passwordless access to Azure SQL for .NET applications that run on Google Cloud. The library turns the application's Google identity into a Microsoft Entra ID access token through workload identity federation; you attach that token to your `SqlConnection`. No database password, client secret or service account key is stored anywhere. @@ -38,18 +38,18 @@ The setup guide linked above walks through each of these step by step. ### 1. Install ```shell -dotnet add package Csag.AzureSqlFederatedIdentity +dotnet add package Csag.WorkloadIdentity ``` The library returns a token string and does not depend on `Microsoft.Data.SqlClient`. Add that package, or an EF Core provider such as `Microsoft.EntityFrameworkCore.SqlServer`, to the project that opens the connections. ### 2. Configure -The options bind from the `Csag.AzureSqlFederatedIdentity` section of the configuration: +The options bind from the `Csag.WorkloadIdentity` section of the configuration: ```json { - "Csag.AzureSqlFederatedIdentity": { + "Csag.WorkloadIdentity": { "TenantId": "", "ClientId": "", "Google": { @@ -72,18 +72,18 @@ The options bind from the `Csag.AzureSqlFederatedIdentity` section of the config | `RefreshAheadWindow` | no | `00:05:00` | How long before the access token expires it is treated as due for refresh. Must be positive. | | `EnableBackgroundRefresh` | no | `true` | Whether a hosted service keeps the token refreshed ahead of its expiry. | -Environment variables follow the usual .NET mapping, with `__` as the section separator: `Csag.AzureSqlFederatedIdentity__TenantId`, `Csag.AzureSqlFederatedIdentity__ClientId`, `Csag.AzureSqlFederatedIdentity__Google__ServiceAccountEmail`, and so on. +Environment variables follow the usual .NET mapping, with `__` as the section separator: `Csag.WorkloadIdentity__TenantId`, `Csag.WorkloadIdentity__ClientId`, `Csag.WorkloadIdentity__Google__ServiceAccountEmail`, and so on. The connection string deliberately carries no credentials; see the notes below. ### 3. Register ```csharp -using Csag.AzureSqlFederatedIdentity; +using Csag.WorkloadIdentity; var builder = WebApplication.CreateBuilder(args); -// Binds the "Csag.AzureSqlFederatedIdentity" section of the host configuration. +// Binds the "Csag.WorkloadIdentity" section of the host configuration. builder.Services.AddAzureSqlFederatedIdentity(); var app = builder.Build(); @@ -93,10 +93,10 @@ app.Run(); Two further overloads exist, for a host whose configuration is not registered as `IConfiguration` in the container and for configuring in code. Given an `IServiceCollection services` and an `IConfiguration configuration`: ```csharp -using Csag.AzureSqlFederatedIdentity; -using Csag.AzureSqlFederatedIdentity.Options; +using Csag.WorkloadIdentity; +using Csag.WorkloadIdentity.Options; -// Binds the "Csag.AzureSqlFederatedIdentity" section of the given configuration. +// Binds the "Csag.WorkloadIdentity" section of the given configuration. services.AddAzureSqlFederatedIdentity(configuration); // Sets the options in code; the section name is available as AzureSqlFederatedIdentityOptions.ConfigurationSectionName. @@ -110,14 +110,14 @@ services.AddAzureSqlFederatedIdentity(options => }); ``` -`GoogleOptions` and `AzureSqlFederatedIdentityOptions` live in `Csag.AzureSqlFederatedIdentity.Options`. The options are validated when the host starts: a missing `TenantId`, `ClientId` or `Google:ServiceAccountEmail`, or a non-positive `RefreshAheadWindow`, throws an `OptionsValidationException` that names the offending value. Calling `AddAzureSqlFederatedIdentity` more than once is harmless. +`GoogleOptions` and `AzureSqlFederatedIdentityOptions` live in `Csag.WorkloadIdentity.Options`. The options are validated when the host starts: a missing `TenantId`, `ClientId` or `Google:ServiceAccountEmail`, or a non-positive `RefreshAheadWindow`, throws an `OptionsValidationException` that names the offending value. Calling `AddAzureSqlFederatedIdentity` more than once is harmless. ### 4. Use the token -Resolve `IAzureSqlTokenProvider` (namespace `Csag.AzureSqlFederatedIdentity.Abstractions`), call `GetAzureSqlAccessTokenAsync` and assign the result to `SqlConnection.AccessToken` before opening the connection. Do this for every new connection: until the held token enters the refresh-ahead window the call returns it without any network round trip; inside the window (reachable only when background refresh is off or has been failing) the first caller exchanges a new token and concurrent callers wait for that one exchange. +Resolve `IAzureSqlTokenProvider` (namespace `Csag.WorkloadIdentity.Abstractions`), call `GetAzureSqlAccessTokenAsync` and assign the result to `SqlConnection.AccessToken` before opening the connection. Do this for every new connection: until the held token enters the refresh-ahead window the call returns it without any network round trip; inside the window (reachable only when background refresh is off or has been failing) the first caller exchanges a new token and concurrent callers wait for that one exchange. ```csharp -using Csag.AzureSqlFederatedIdentity.Abstractions; +using Csag.WorkloadIdentity.Abstractions; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; @@ -145,7 +145,7 @@ Register the class as usual, for example `builder.Services.AddScoped();`) and - **Runtime identity.** The Google ID token is requested through Application Default Credentials (ADC). Whatever identity ADC resolves to (the service account attached to the Cloud Run, GKE or Compute Engine resource, or your own account after `gcloud auth application-default login`) must hold `roles/iam.serviceAccountOpenIdTokenCreator` on the configured `ServiceAccountEmail`. The intended deployment is the simplest one: the application runs *as* that service account, with the role granted to the account on itself. - **Token handling.** The provider holds the token; do not cache or persist it yourself. `AccessToken` is part of the `SqlClient` connection pool key, so a refreshed token starts a new pool, which is expected. If your connection string sets `Min Pool Size` above zero, call `SqlConnection.ClearPool` with a connection that carries the old token once it has expired; otherwise the pool keeps that token's physical connections open indefinitely (see the [`AccessToken` remarks](https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlconnection.accesstoken)). - **Background refresh.** When enabled, the hosted service exchanges a token as soon as the host starts and again when the held token enters the refresh-ahead window, or at half the token's remaining lifetime when that is shorter than the window (the wait is kept between 10 seconds and 1 day). A failed exchange is logged at `Error` and retried, waiting 5 seconds and doubling up to 5 minutes between attempts. When disabled, the first caller to find the token due for refresh performs the exchange while concurrent callers wait for its result. If you register your own `IAzureSqlTokenProvider`, the hosted service leaves it alone. -- **Logging.** All categories start with `Csag.AzureSqlFederatedIdentity`. Exchanges and refreshes log at `Debug`; per-call reuse of the held token logs at `Trace`. +- **Logging.** All categories start with `Csag.WorkloadIdentity`. Exchanges and refreshes log at `Debug`; per-call reuse of the held token logs at `Trace`. ## Troubleshooting diff --git a/Csag.AzureSqlFederatedIdentity/icon.png b/Csag.WorkloadIdentity/icon.png similarity index 100% rename from Csag.AzureSqlFederatedIdentity/icon.png rename to Csag.WorkloadIdentity/icon.png diff --git a/Csag.WorkloadIdentity/package.json b/Csag.WorkloadIdentity/package.json new file mode 100644 index 0000000..67266ab --- /dev/null +++ b/Csag.WorkloadIdentity/package.json @@ -0,0 +1,5 @@ +{ + "name": "@neolution-ch/csag-workload-identity", + "version": "0.0.1", + "private": true +} diff --git a/Csag.AzureSqlFederatedIdentity/packages.lock.json b/Csag.WorkloadIdentity/packages.lock.json similarity index 100% rename from Csag.AzureSqlFederatedIdentity/packages.lock.json rename to Csag.WorkloadIdentity/packages.lock.json diff --git a/README.md b/README.md index 3936cd2..ea2ca2b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Csag.AzureSqlFederatedIdentity +# Csag.WorkloadIdentity [![Build Status](https://github.com/neolution-ch/Neolution.AzureSqlFederatedIdentity/actions/workflows/ci.yml/badge.svg)](https://github.com/neolution-ch/Neolution.AzureSqlFederatedIdentity/actions) -[![NuGet](https://img.shields.io/nuget/v/Csag.AzureSqlFederatedIdentity.svg)](https://www.nuget.org/packages/Csag.AzureSqlFederatedIdentity) +[![NuGet](https://img.shields.io/nuget/v/Csag.WorkloadIdentity.svg)](https://www.nuget.org/packages/Csag.WorkloadIdentity) [![License: MIT](https://img.shields.io/badge/License-MIT-lightgray.svg)](./LICENSE) A .NET library that lets an application running on Google Cloud connect to Azure SQL without a password, secret or key. It exchanges the application's Google identity for a Microsoft Entra ID access token through workload identity federation (Google-signed ID token → Microsoft Entra ID access token) and you attach that token to the `SqlConnection`. @@ -10,20 +10,20 @@ A .NET library that lets an application running on Google Cloud connect to Azure | Project | Description | |---|---| -| [`Csag.AzureSqlFederatedIdentity`](./Csag.AzureSqlFederatedIdentity) | The library, published as the [Csag.AzureSqlFederatedIdentity](https://www.nuget.org/packages/Csag.AzureSqlFederatedIdentity) NuGet package for `net8.0` and `net10.0`. Its [README](./Csag.AzureSqlFederatedIdentity/README.md) is the package documentation; the [CHANGELOG](./Csag.AzureSqlFederatedIdentity/CHANGELOG.md) is generated from changesets. | -| [`Csag.AzureSqlFederatedIdentity.Demo`](./Csag.AzureSqlFederatedIdentity.Demo) | An ASP.NET Core application for Cloud Run that reads from Azure SQL through the library. Its [README](./Csag.AzureSqlFederatedIdentity.Demo/README.md) explains how to run it from source, in Docker and on Cloud Run. | -| [`Csag.AzureSqlFederatedIdentity.UnitTests`](./Csag.AzureSqlFederatedIdentity.UnitTests) | xunit tests for the library, run on both target frameworks. | +| [`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.UnitTests`](./Csag.WorkloadIdentity.UnitTests) | xunit tests for the library, run on both target frameworks. | ## Quick start 1. Set up the cloud side once: the Google service account and its IAM role, the Microsoft Entra ID app registration with a federated credential, the Azure SQL database user and the Cloud Run runtime identity. Every step is in [docs/cloud-identity-setup.md](./docs/cloud-identity-setup.md). -2. Install the package and wire it into your application. The [package README](./Csag.AzureSqlFederatedIdentity/README.md) has the complete quickstart: configuration keys, service registration and attaching the token to `SqlConnection`, with plain ADO.NET and with EF Core. +2. Install the package and wire it into your application. The [package README](./Csag.WorkloadIdentity/README.md) has the complete quickstart: configuration keys, service registration and attaching the token to `SqlConnection`, with plain ADO.NET and with EF Core. ```shell - dotnet add package Csag.AzureSqlFederatedIdentity + dotnet add package Csag.WorkloadIdentity ``` -3. To see it end to end, run the [Demo](./Csag.AzureSqlFederatedIdentity.Demo/README.md) against your own database. +3. To see it end to end, run the [Demo](./Csag.WorkloadIdentity.Demo/README.md) against your own database. ## Release process diff --git a/SECURITY.md b/SECURITY.md index 36bd393..208fc4b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ # Security policy -Csag.AzureSqlFederatedIdentity handles credentials: it obtains Google ID tokens and Microsoft Entra ID access tokens and keeps the current access token in memory. We take reports about it seriously. +Csag.WorkloadIdentity handles credentials: it obtains Google ID tokens and Microsoft Entra ID access tokens and keeps the current access token in memory. We take reports about it seriously. ## Supported versions @@ -27,6 +27,6 @@ We acknowledge every report, keep you informed while we investigate, and coordin ## Scope -- In scope: the `Csag.AzureSqlFederatedIdentity` library. +- In scope: the `Csag.WorkloadIdentity` library. - The Demo project is a sample and is not meant for production use; reports about it are still welcome if they point to a problem in the library or its documentation. - Vulnerabilities in dependencies such as `Azure.Identity` or the Google Cloud client libraries should be reported to their maintainers; let us know as well if the library needs to update in response. diff --git a/docs/cloud-identity-setup.md b/docs/cloud-identity-setup.md index ab9cf81..c0e61cc 100644 --- a/docs/cloud-identity-setup.md +++ b/docs/cloud-identity-setup.md @@ -1,6 +1,6 @@ # Cloud and identity setup -This guide sets up everything outside your code so that an application on Google Cloud can connect to Azure SQL through `Csag.AzureSqlFederatedIdentity`: the Google Cloud service account and its permissions, the app registration and federated credential in Microsoft Entra ID (formerly Azure Active Directory), the database user in Azure SQL, the application's configuration and the Cloud Run deployment. For the code that uses the token, see the [package README](../Csag.AzureSqlFederatedIdentity/README.md). +This guide sets up everything outside your code so that an application on Google Cloud can connect to Azure SQL through `Csag.WorkloadIdentity`: the Google Cloud service account and its permissions, the app registration and federated credential in Microsoft Entra ID (formerly Azure Active Directory), the database user in Azure SQL, the application's configuration and the Cloud Run deployment. For the code that uses the token, see the [package README](../Csag.WorkloadIdentity/README.md). ## How the pieces fit @@ -146,21 +146,21 @@ The server's [network access controls](https://learn.microsoft.com/en-us/azure/a ## 4. Configure the application -The library binds its options from the `Csag.AzureSqlFederatedIdentity` configuration section. These are the exact keys, with the environment variable form that .NET maps to the same keys (`__` stands for the `:` separator; the `.` in the section name is part of the variable name): +The library binds its options from the `Csag.WorkloadIdentity` configuration section. These are the exact keys, with the environment variable form that .NET maps to the same keys (`__` stands for the `:` separator; the `.` in the section name is part of the variable name): | Configuration key | Environment variable | Value | |---|---|---| -| `Csag.AzureSqlFederatedIdentity:TenantId` | `Csag.AzureSqlFederatedIdentity__TenantId` | Directory (tenant) ID (step 2.1) | -| `Csag.AzureSqlFederatedIdentity:ClientId` | `Csag.AzureSqlFederatedIdentity__ClientId` | Application (client) ID (step 2.1) | -| `Csag.AzureSqlFederatedIdentity:Google:ServiceAccountEmail` | `Csag.AzureSqlFederatedIdentity__Google__ServiceAccountEmail` | Service account email (step 1.2) | -| `Csag.AzureSqlFederatedIdentity:RefreshAheadWindow` | `Csag.AzureSqlFederatedIdentity__RefreshAheadWindow` | Optional; how long before expiry the token is refreshed. Default `00:05:00`, must be positive | -| `Csag.AzureSqlFederatedIdentity:EnableBackgroundRefresh` | `Csag.AzureSqlFederatedIdentity__EnableBackgroundRefresh` | Optional; default `true` | +| `Csag.WorkloadIdentity:TenantId` | `Csag.WorkloadIdentity__TenantId` | Directory (tenant) ID (step 2.1) | +| `Csag.WorkloadIdentity:ClientId` | `Csag.WorkloadIdentity__ClientId` | Application (client) ID (step 2.1) | +| `Csag.WorkloadIdentity:Google:ServiceAccountEmail` | `Csag.WorkloadIdentity__Google__ServiceAccountEmail` | Service account email (step 1.2) | +| `Csag.WorkloadIdentity:RefreshAheadWindow` | `Csag.WorkloadIdentity__RefreshAheadWindow` | Optional; how long before expiry the token is refreshed. Default `00:05:00`, must be positive | +| `Csag.WorkloadIdentity:EnableBackgroundRefresh` | `Csag.WorkloadIdentity__EnableBackgroundRefresh` | Optional; default `true` | The first three are required; the application refuses to start if any of them is missing. In `appsettings.json`: ```json { - "Csag.AzureSqlFederatedIdentity": { + "Csag.WorkloadIdentity": { "TenantId": "", "ClientId": "", "Google": { @@ -173,7 +173,7 @@ The first three are required; the application refuses to start if any of them is } ``` -The connection string is your own application's setting (the Demo reads `ConnectionStrings:DefaultConnection`). It names only the server and database: the access token is the credential, so it must not contain `User ID`/`Password`, `Integrated Security` or an `Authentication` keyword, and `Encrypt=True` keeps the token and the data on TLS. The [package README](../Csag.AzureSqlFederatedIdentity/README.md) shows how to register the library and attach the token to a `SqlConnection`. +The connection string is your own application's setting (the Demo reads `ConnectionStrings:DefaultConnection`). It names only the server and database: the access token is the credential, so it must not contain `User ID`/`Password`, `Integrated Security` or an `Authentication` keyword, and `Encrypt=True` keeps the token and the data on TLS. The [package README](../Csag.WorkloadIdentity/README.md) shows how to register the library and attach the token to a `SqlConnection`. ## 5. Cloud Run @@ -181,9 +181,9 @@ Deploy the application with the service account from step 1 as its runtime ident ```yaml # env.yaml -Csag.AzureSqlFederatedIdentity__TenantId: "" -Csag.AzureSqlFederatedIdentity__ClientId: "" -Csag.AzureSqlFederatedIdentity__Google__ServiceAccountEmail: "@.iam.gserviceaccount.com" +Csag.WorkloadIdentity__TenantId: "" +Csag.WorkloadIdentity__ClientId: "" +Csag.WorkloadIdentity__Google__ServiceAccountEmail: "@.iam.gserviceaccount.com" ConnectionStrings__DefaultConnection: "Server=tcp:.database.windows.net,1433;Initial Catalog=;Encrypt=True" ``` @@ -198,7 +198,7 @@ gcloud run deploy \ To change only the identity of an existing service, use `gcloud run services update --service-account @.iam.gserviceaccount.com`. In the console, the runtime service account is under the service's **Security** tab and the variables under **Variables & Secrets**. -At startup the application validates its configuration: the three required settings must be present and `RefreshAheadWindow` must be positive. With background refresh on (the default) the first token exchange happens right after startup; with it off, on the first database access. Either way the application requests an ID token for the configured service account through ADC (as the runtime service account), exchanges it at Microsoft Entra ID, and opens the connection with the resulting access token. If something fails, the application log names the failing step; the troubleshooting table in the [package README](../Csag.AzureSqlFederatedIdentity/README.md) maps the usual messages to their cause. +At startup the application validates its configuration: the three required settings must be present and `RefreshAheadWindow` must be positive. With background refresh on (the default) the first token exchange happens right after startup; with it off, on the first database access. Either way the application requests an ID token for the configured service account through ADC (as the runtime service account), exchanges it at Microsoft Entra ID, and opens the connection with the resulting access token. If something fails, the application log names the failing step; the troubleshooting table in the [package README](../Csag.WorkloadIdentity/README.md) maps the usual messages to their cause. ## References diff --git a/package-lock.json b/package-lock.json index 7f002d5..fdc5937 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { - "name": "csag-azure-sql-federated-identity", + "name": "csag-workload-identity", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "csag-azure-sql-federated-identity", + "name": "csag-workload-identity", "workspaces": [ - "Csag.AzureSqlFederatedIdentity" + "Csag.WorkloadIdentity" ], "devDependencies": { "@changesets/changelog-github": "^1.0.1", "@changesets/cli": "^3.0.3" } }, - "Csag.AzureSqlFederatedIdentity": { - "name": "@neolution-ch/csag-azure-sql-federated-identity", + "Csag.WorkloadIdentity": { + "name": "@neolution-ch/csag-workload-identity", "version": "0.0.1" }, "node_modules/@changesets/apply-release-plan": { @@ -359,8 +359,8 @@ "node": ">=20.0.0" } }, - "node_modules/@neolution-ch/csag-azure-sql-federated-identity": { - "resolved": "Csag.AzureSqlFederatedIdentity", + "node_modules/@neolution-ch/csag-workload-identity": { + "resolved": "Csag.WorkloadIdentity", "link": true }, "node_modules/@pnpm/deps.graph-sequencer": { diff --git a/package.json b/package.json index 8345ba0..a9efdba 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "csag-azure-sql-federated-identity", + "name": "csag-workload-identity", "private": true, "workspaces": [ - "Csag.AzureSqlFederatedIdentity" + "Csag.WorkloadIdentity" ], "scripts": { "changeset": "changeset",