Skip to content

Add Bitwarden Secrets Manager hosting and client integrations#1329

Open
sliekens wants to merge 93 commits into
CommunityToolkit:mainfrom
sliekens:bitwarden-secret-manager-integration
Open

Add Bitwarden Secrets Manager hosting and client integrations#1329
sliekens wants to merge 93 commits into
CommunityToolkit:mainfrom
sliekens:bitwarden-secret-manager-integration

Conversation

@sliekens

@sliekens sliekens commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Add Bitwarden Secrets Manager hosting and client integrations for Aspire.

This targets Bitwarden Secrets Manager, the application and machine secret management product, rather than the Bitwarden Password Manager end-user vault. The AppHost can now provision and manage a Bitwarden project as a first-class resource, with managed secret definitions attached to it. It can also pass structured configuration to consuming services and adopt existing Bitwarden projects or secrets when needed.

Why

This removes the need for ad hoc Bitwarden bootstrap scripts and manual client wiring by letting the AppHost provision a Bitwarden project and its managed secrets and pass structured configuration to consuming services. It also supports adopting existing Bitwarden projects or secrets when a greenfield setup is not appropriate.

What changed

  • Add a non-container AppHost resource for provisioning and managing a Bitwarden project, with managed secret definitions attached to it.
  • Add a companion client integration that registers authenticated Bitwarden clients for consuming services.
  • Support adopting existing Bitwarden projects or secrets when a greenfield setup is not appropriate.
  • Add sample usage, unit tests, and the necessary solution, documentation, and CI wiring for the new integrations.

Design note

This follows a similar shape to Azure Key Vault in Aspire: the AppHost models Bitwarden Secrets Manager as an external managed secret store rather than as a local containerized dependency. Bitwarden Secrets Manager can be self-hosted, but doing so requires an enterprise server license, so a local self-hosted setup is not a practical default for many development teams.

Validation

  • dotnet build
  • dotnet test tests/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.Tests/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.Tests.csproj --no-build
  • dotnet test tests/CommunityToolkit.Aspire.Bitwarden.SecretManager.Tests/CommunityToolkit.Aspire.Bitwarden.SecretManager.Tests.csproj --no-build

Hosting-side tests currently use a fake provider rather than a live Bitwarden tenant.

I manually tested this against a real Bitwarden instance using the example project included in this pull request.

Planning Docs

The original planning documents I gave to my agent:

You might notice there were some iterations on the original plan, so the PLAN document doesn't accurately reflect the current state of this pull request.

The final architecture is documented in ARCHITECTURE.md and has been expanded and kept up-to-date while iterating on the original design.

@github-actions

github-actions Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/CommunityToolkit/Aspire/main/eng/scripts/dogfood-pr.sh | bash -s -- 1329

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/CommunityToolkit/Aspire/main/eng/scripts/dogfood-pr.ps1) } 1329"

@sliekens sliekens changed the title Add Bitwarden Secrets Manager integrations Add Bitwarden Secrets Manager hosting and client integrations May 19, 2026
@sliekens sliekens marked this pull request as ready for review May 19, 2026 23:15
Copilot AI review requested due to automatic review settings May 19, 2026 23:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds new Aspire Community Toolkit integrations for Bitwarden Secrets Manager: a hosting-side resource that reconciles a Bitwarden project + managed secrets during AppHost startup, and a client-side integration that registers authenticated BitwardenClient instances from structured Aspire configuration.

Changes:

  • Introduces BitwardenSecretManagerResource + reconciler/state store to provision/adopt projects and create/adopt/update secrets, and to publish manifest + structured configuration via WithReference(...).
  • Adds a client integration (AddBitwardenSecretManagerClient / keyed variant) with settings binding, validation, and optional health checks.
  • Adds tests, documentation, CI wiring, and a runnable example AppHost + consumer service.

Reviewed changes

Copilot reviewed 30 out of 30 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.Tests/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.Tests.csproj Adds hosting integration test project.
tests/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.Tests/BitwardenSecretManagerReconcilerTests.cs Tests reconciler behavior (create/adopt/update project + secrets) using a fake provider.
tests/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.Tests/BitwardenSecretManagerBuilderTests.cs Tests builder/resource model behavior and WithReference environment injection.
tests/CommunityToolkit.Aspire.Bitwarden.SecretManager.Tests/CommunityToolkit.Aspire.Bitwarden.SecretManager.Tests.csproj Adds client integration test project.
tests/CommunityToolkit.Aspire.Bitwarden.SecretManager.Tests/BitwardenSecretManagerClientPublicApiTests.cs Verifies client extension methods validate inputs.
tests/CommunityToolkit.Aspire.Bitwarden.SecretManager.Tests/AspireBitwardenSecretManagerExtensionsTests.cs Tests client settings binding + health check registration behavior.
src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/README.md Documents hosting integration usage and configuration.
src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.csproj Adds hosting package project + dependencies.
src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/BitwardenSecretResource.cs Adds managed secret resource type and value/expression behavior.
src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/BitwardenSecretReference.cs Adds public secret reference interface + internal reference implementation.
src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/BitwardenSecretManagerResource.cs Implements the Bitwarden project resource model and reference/env var support.
src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/BitwardenSecretManagerReconciler.cs Implements startup reconciliation + state persistence + provider abstraction.
src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/BitwardenSecretManagerExtensions.cs Adds AppHost builder extensions, manifest publishing, and initialization wiring.
src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/AssemblyInfo.cs Exposes internals to hosting tests.
src/CommunityToolkit.Aspire.Bitwarden.SecretManager/README.md Documents client integration configuration and usage.
src/CommunityToolkit.Aspire.Bitwarden.SecretManager/CommunityToolkit.Aspire.Bitwarden.SecretManager.csproj Adds client package project + dependencies.
src/CommunityToolkit.Aspire.Bitwarden.SecretManager/BitwardenSecretManagerHealthCheck.cs Adds health check implementation for the client integration.
src/CommunityToolkit.Aspire.Bitwarden.SecretManager/BitwardenSecretManagerClientSettings.cs Adds settings POCO for client configuration.
src/CommunityToolkit.Aspire.Bitwarden.SecretManager/AspireBitwardenSecretManagerExtensions.cs Adds client registration extensions (keyed + unkeyed), binding, validation, and health checks.
README.md Adds integration entries + NuGet/doc links for Bitwarden Secrets Manager.
examples/bitwarden-secret-manager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.AppHost/Properties/launchSettings.json Adds example AppHost launch profiles.
examples/bitwarden-secret-manager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.AppHost/Program.cs Adds example AppHost using the hosting integration + passing config to a consumer.
examples/bitwarden-secret-manager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.AppHost/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.AppHost.csproj Adds example AppHost project.
examples/bitwarden-secret-manager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.AppHost/aspire.config.json Adds aspire config pointing to the AppHost project.
examples/bitwarden-secret-manager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.ApiService/Properties/launchSettings.json Adds example consumer service launch profiles.
examples/bitwarden-secret-manager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.ApiService/Program.cs Adds example consumer service using the client integration.
examples/bitwarden-secret-manager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.ApiService/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.ApiService.csproj Adds example consumer service project.
Directory.Packages.props Adds Bitwarden.Secrets.Sdk package version.
CommunityToolkit.Aspire.slnx Adds new src/tests projects and example projects to the solution.
.github/workflows/tests.yaml Wires new hosting + client test projects into CI.
Comments suppressed due to low confidence (2)

src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/BitwardenSecretManagerExtensions.cs:127

  • This overload also dereferences projectName.Resource without validating projectName for null, which can lead to NullReferenceException for invalid caller input. Add ArgumentNullException.ThrowIfNull(projectName); to match the validation used for other IResourceBuilder<> parameters.
    public static IResourceBuilder<BitwardenSecretManagerResource> AddBitwardenSecretManager(
        this IDistributedApplicationBuilder builder,
        [ResourceName] string name,
        IResourceBuilder<ParameterResource> projectName,
        IResourceBuilder<ParameterResource> organizationId,
        IResourceBuilder<ParameterResource> accessToken)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentException.ThrowIfNullOrWhiteSpace(name);
        ArgumentNullException.ThrowIfNull(organizationId);
        ArgumentNullException.ThrowIfNull(accessToken);

        return AddBitwardenSecretManagerCore(
            builder,
            name,
            ConfiguredStringValue.FromParameter(projectName.Resource),
            ConfiguredGuidValue.FromParameter(organizationId.Resource),
            accessToken);

src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/BitwardenSecretManagerExtensions.cs:275

  • This AddSecret overload (ReferenceExpression) also doesn't validate builder for null before using it, which can produce NullReferenceException for invalid caller input. Add ArgumentNullException.ThrowIfNull(builder); to align with the rest of the public API surface.
    /// <summary>
    /// Adds a managed Bitwarden secret whose local and remote names are the same.
    /// </summary>
    /// <param name="builder">The parent Bitwarden resource builder.</param>
    /// <param name="name">The Aspire resource name and Bitwarden secret name.</param>
    /// <param name="value">The secret value expression.</param>
    /// <returns>The managed secret resource builder.</returns>
    public static IResourceBuilder<BitwardenSecretResource> AddSecret(
        this IResourceBuilder<BitwardenSecretManagerResource> builder,
        [ResourceName] string name,
        ReferenceExpression value)
    {
        ArgumentNullException.ThrowIfNull(value);
        return builder.AddSecret(name, name, value);
    }

@sliekens sliekens marked this pull request as draft May 20, 2026 18:21
@sliekens sliekens marked this pull request as ready for review May 20, 2026 19:39
@sliekens sliekens marked this pull request as draft May 21, 2026 21:46
@sliekens sliekens marked this pull request as ready for review May 21, 2026 22:50
@sliekens sliekens marked this pull request as draft May 22, 2026 20:55
@sliekens

Copy link
Copy Markdown
Contributor Author

Back in draft, I'm not happy with the (lack of a real) deployment model.
New plan: rewrite secret provisioning as pipeline steps instead of in InitializeAsync.

@sliekens

sliekens commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

This is ready for review , I'm happy with the current state of this branch.
The deployment story is pretty good now, centered around pipelines instead of the manifest.

Dogfooding
As an external contributor, I don't have a way to run the dogfooding workflow.

However, I did publish a preview version to my own nuget feed, which you can use for testing.

Project type Install command Usage instructions
AppHost dotnet add package CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager --version 13.4.0-preview.3 --source https://nuget.sliekens.dev/v3/index.json README.md
Client dotnet add package CommunityToolkit.Aspire.Bitwarden.SecretManager --version 13.4.0-preview.3 --source https://nuget.sliekens.dev/v3/index.json README.md

Version 13.4.0-preview.3 packages were built from commit 356b6ca.

@Omnideth Omnideth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this having only looked at 36/39 files. I still have a few test classes I'm too tired to complete right now, but wanted to submit the feedback.

I want to note that I've not personally used BitWarden, so I am mostly looking at it from a meta perspective.

In general, nothing looked extremely out of place. I commented where I thought maybe Aaron nudged me on another PR.

- Using eventing subscribers as the deployment integration point for publishing.
- Making runtime reconciliation the primary architectural concept.

The intended design is pipeline-step-first, declared-resource-first.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if Aaron's suggestion to me would apply to this and the ASPIRE-INTERNALS.md

#1155 (comment)

These seem like detailed documentation rather than a quick start guide. I think the repo standard is a quick start guide in README.md and then any more detailed documentation would be PR'd to aspire.dev after merging to Main and the nuget package gets published.

1. Check whether `ASPIREATS001`, `ASPIREPIPELINES001`, `ASPIREPIPELINES002`, `ASPIREPIPELINES004`, `ASPIREINTERACTION001` have moved from `[Experimental]` to stable — if so, remove the corresponding `#pragma` suppressions.
2. Run the AppHost and check for `MissingMethodException` / `MissingFieldException` from the `UnsafeAccessor` targets above.
3. Run `aspire deploy` end-to-end and verify that (a) managed secrets are not prompted when they exist in Bitwarden, (b) reference secrets are not prompted, and (c) the "Parameters need values" banner disappears automatically in run mode.
4. Check whether `DistributedApplicationBuilder.cs` still adds the deployment state file as a JSON configuration source (`AddJsonFile`), and whether the state file format produced by `FileDeploymentStateManager` is still compatible with the JSON configuration provider key format.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I said what I said on ARCHITECTURE.md, but also, this looks almost like agent guidance for future changes. Who is this documentation for? The developer in the chair using the hosting integration or the developer making updates to the hosting integration?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is basically an apology to future me and any other maintainers for what you're about to see, along with some upgrade hints for when the Aspire internals eventually do change, so I would put it squarely in the "for contributors" box.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed so. My follow up here is:

Maybe we should think about standardizing a way to add LLM guidance in a given integration.

@sliekens sliekens force-pushed the bitwarden-secret-manager-integration branch from b687e42 to 9db24c3 Compare June 4, 2026 16:52
sliekens added 9 commits June 4, 2026 18:13
Because I can't design BitwardenSecretResource as a ParameterResource while also preventing use of WithEnvironment, it makes more sense to design for WithEnvironment only.

Old: WithBitwardenSecretValue(name, secret)
New: WithEnvironment(name, secret)

Old: WithBitwardenSecretId(name, secret)
New: WithEnvironment(name, secret.AsSecretId())
@sliekens sliekens force-pushed the bitwarden-secret-manager-integration branch from b2732d7 to 69a2d18 Compare June 5, 2026 22:21
@sliekens

sliekens commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

I did a few more API simplifications, added coverage for edge cases (and fixed the failing ones), fixed an issue where config reloading in deploy mode caused weird prompting behavior, added deploy debug logging aspire deploy --pipeline-log-level debug and updated to Aspire 13.4.

API changes

  • WithBitwardenSecretValue/Id is now WithEnvironment(name, value) for value or value.AsSecretId()) for id
  • implicit WaitForCompletion(bitwarden) is gone, must now be called explicitly, following the pattern established by other integrations
  • Removed overloads that accept a simple type (string / Guid) instead of IResourceBuilder<ParameterResource>
  • Removed WithExistingProjectId, the project parameter can now be either a name or an ID
  • Removed WithExistingSecretId, you can now only pass a secret id to GetSecret(...)

The last two changes forces usage into one of two modes of operation:

  1. Aspire manages secrets (create projects/secrets if not exist, update if local parameter differs from remote value)
  2. Externally managed secrets (read-only, fail if projects/secrets not exist, never update remote)

It allows a mix of scenarios

  1. Application secrets are completely managed by Aspire
  2. Some application secrets are managed by Aspire, others are managed externally
  3. All application secrets are managed externally

Preview packages were updated for Aspire 13.4, see #1329 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants