Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/build-hygiene.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion .changeset/docs-usage-and-guides.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 3 additions & 3 deletions .changeset/rename-to-csag.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions .changeset/token-provider-hardening.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"@neolution-ch/csag-azure-sql-federated-identity": minor
"@neolution-ch/csag-workload-identity": minor
---

Harden token acquisition, refresh and registration.
Expand All @@ -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<AzureSqlFederatedIdentityOptions>` 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<AzureSqlFederatedIdentityOptions>` 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.
4 changes: 2 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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/
Expand All @@ -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
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dependabot-changeset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:"
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/nuget-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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: |
Expand Down
10 changes: 5 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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.
Expand All @@ -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_<state>_When_<action>_Then_<outcome>` 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_<state>_When_<action>_Then_<outcome>` naming with Arrange/Act/Assert sections, and live in `Csag.WorkloadIdentity.UnitTests`. Read an existing test class before adding one.

## Dependencies

Expand All @@ -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.
Expand Down
26 changes: 0 additions & 26 deletions Csag.AzureSqlFederatedIdentity.Demo/Dockerfile

This file was deleted.

5 changes: 0 additions & 5 deletions Csag.AzureSqlFederatedIdentity/package.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Csag.AzureSqlFederatedIdentity\Csag.AzureSqlFederatedIdentity.csproj" />
<ProjectReference Include="..\Csag.WorkloadIdentity\Csag.WorkloadIdentity.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -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<AppDbContext> options) : DbContext(options)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Csag.AzureSqlFederatedIdentity.Demo.Database
namespace Csag.WorkloadIdentity.Demo.Database
{
public interface IAppDbContextFactory
{
Expand Down
26 changes: 26 additions & 0 deletions Csag.WorkloadIdentity.Demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Csag.AzureSqlFederatedIdentity.Demo.Entities
namespace Csag.WorkloadIdentity.Demo.Entities
{
public class TestEntity
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
Loading
Loading