Skip to content

Commit 63d4e08

Browse files
Add Bitwarden Secrets Manager hosting and client integrations (#1329)
* Add Bitwarden Secrets Manager integration * Add missing launch config to bitwarden example * Convert bitwarden project name to required parameter * Remove unused internal project name tracking * Convert fake async to plain synchronous helper * Collapse the public overload matrix to one internal representation * Add test for same-name AddSecret/GetSecret * Expand sample with secret retrieval * Add ssl cert config for bitwarden sdk * Add first-class secret projection API * Remove unused setter * Add missing null/empty/whitespace guards * Make secret name matching case insensitive * Skip Secrets.Update() for unchanged secrets * Add missing logging * Make auth cache actually work and improve example * Replace manifest with pipeline steps * Add compose env to example * Improve apphost state handling with IAspireStore * Pin ssl cert dir in run mode * Ignore transient errors * Fix argument passing * Configure auth state file as parameter * Reorganize types into logical groups * Fix redundant parameter prompting * Reorganize auth and project cache files * Add env patch for resolved secrets * Split deployment pipeline by concern * Update bitwarden docs * Disable deployed dashboard for example * Make deployed example endpoints external * Tag 'provision-project' as 'provision-infra' too * Make default cache path more predictable New default: <AppHost>/.bws/{resourceName}.{environment}.json e.g. AppHost/.bws/secrets.Development.json AppHost/.bws/secrets.Production.json * Use unique one-way hash for auth cache New default: <AppHost>/obj/.bitwarden/<tokenHash>.auth-cache .e.g. AppHost/obj/.bitwarden/a3f9c1d.auth-cache (Also rename folder .bws to .bitwarden for the project cache) * Replace WithRuntimeAccessToken with overload of WithReference * Redesign client integration API as configuration callback * Update gitignore * Improve state transitions and document decisions * Add commands to reset auth cache and reprovision secrets * Highlight sync command, move reset auth cache to extras * Handle transient bitwarden errors * Add explicit TLS certificate validation * Make resource states less confusing * Add parameter prompts * Add a change audit trail * Deduplicate initial and repeated sync code * Fix nonzero exit code after succesful retry * Improve state transitions * Add parameters to DAG * Add new ways to configure URLs * Allow resetting auth cache from any state * Use `Waiting` instead of `ValueMissing` when params are not set * Use UnsafeAccessor to check if parameter has a value * Unify managed and unmanaged secrets * Reorganize optional config in readme * Add Compatibility notes to readme * Fix unmanaged secret value missing from env * Fix deploy prompting values for unmanaged secrets * Pre-sync managed secrets before process-parameters * Add warnings and fallbacks when internals change * Add WithAuthCacheFile placeholder to sample app * Add WithAuthCacheVolume for container resources * Fix stale docs * Allign auth caches with how bws cli does it * Add persistent auth cache example * Add simple curl client to example * Keep project and secret provisioning docs together * Add unmanaged secret resolution doc * Remove stray UserSecretsId * Update description to "it's just Aspire" * Improve ATS annotations * Replace callback API with optional chaining for ATS compatibility * Rework keyed service tests * Align GetSecret with AddSecret * Rework readme to add missing details, remove excessive details or repetition * fixup! Replace callback API with optional chaining for ATS compatibility * Make BitwardenSecretResource provide the value without indirection * Don't implicitly WaitForCompletion, it's not a common pattern * Drop WithBitwardenSecretId/Value in favor of WithEnvironment 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()) * Fix outdated docs * Remove unnecessary local secret name * Swap reprovision/reset cache commands * Fix repeated prompts on first deploy, improve first-time experience * Introduce clear split between managed vs. externally managed secrets * Fix highlighted command test * Collapse AddBitwardenSecretManager overloads to a single method * Rename projectName in sample * Cover blind spots in BitwardenSecretManagerProvisioner tests * Fix reloading deployment state on first run * Log descriptive errors for missing parameters in non-interactive deploys * Align project deploy rules between publish and run mode * Update examples/bitwarden-secret-manager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.AppHost/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.AppHost.csproj * Update src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.csproj * Apply suggestion from @aaronpowell * Apply suggestion from @aaronpowell --------- Co-authored-by: Aaron Powell <me@aaron-powell.com>
1 parent 8e57335 commit 63d4e08

43 files changed

Lines changed: 7546 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CommunityToolkit.Aspire.slnx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
<Folder Name="/examples/azure-ext/">
1212
<Project Path="examples/azure-ext/CommunityToolkit.Aspire.Azure.Extensions.AppHost/CommunityToolkit.Aspire.Azure.Extensions.AppHost.csproj" />
1313
</Folder>
14+
<Folder Name="/examples/bitwarden-secret-manager/">
15+
<Project Path="examples/bitwarden-secret-manager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.ApiService/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.ApiService.csproj" />
16+
<Project Path="examples/bitwarden-secret-manager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.AppHost/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.AppHost.csproj" />
17+
</Folder>
1418
<Folder Name="/examples/bun/">
1519
<Project Path="examples/bun/CommunityToolkit.Aspire.Hosting.Bun.AppHost/CommunityToolkit.Aspire.Hosting.Bun.AppHost.csproj" />
1620
</Folder>
@@ -218,11 +222,13 @@
218222
<Project Path="examples\zitadel\CommunityToolkit.Aspire.Hosting.Zitadel.AppHost\CommunityToolkit.Aspire.Hosting.Zitadel.AppHost.csproj" />
219223
</Folder>
220224
<Folder Name="/src/">
225+
<Project Path="src/CommunityToolkit.Aspire.Bitwarden.SecretManager/CommunityToolkit.Aspire.Bitwarden.SecretManager.csproj" />
221226
<Project Path="src/CommunityToolkit.Aspire.GoFeatureFlag/CommunityToolkit.Aspire.GoFeatureFlag.csproj" />
222227
<Project Path="src/CommunityToolkit.Aspire.Hosting.ActiveMQ/CommunityToolkit.Aspire.Hosting.ActiveMQ.csproj" />
223228
<Project Path="src/CommunityToolkit.Aspire.Hosting.Adminer/CommunityToolkit.Aspire.Hosting.Adminer.csproj" />
224229
<Project Path="src/CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder/CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder.csproj" />
225230
<Project Path="src/CommunityToolkit.Aspire.Hosting.Azure.Extensions/CommunityToolkit.Aspire.Hosting.Azure.Extensions.csproj" />
231+
<Project Path="src/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.csproj" />
226232
<Project Path="src/CommunityToolkit.Aspire.Hosting.Bun/CommunityToolkit.Aspire.Hosting.Bun.csproj" />
227233
<Project Path="src/CommunityToolkit.Aspire.Hosting.DbGate/CommunityToolkit.Aspire.Hosting.DbGate.csproj" />
228234
<Project Path="src/CommunityToolkit.Aspire.Hosting.Dbx/CommunityToolkit.Aspire.Hosting.Dbx.csproj" />
@@ -288,11 +294,13 @@
288294
<Project Path="src/CommunityToolkit.Aspire.Hosting.Dapr/CommunityToolkit.Aspire.Hosting.Dapr.csproj" />
289295
</Folder>
290296
<Folder Name="/tests/">
297+
<Project Path="tests/CommunityToolkit.Aspire.Bitwarden.SecretManager.Tests/CommunityToolkit.Aspire.Bitwarden.SecretManager.Tests.csproj" />
291298
<Project Path="tests/CommunityToolkit.Aspire.GoFeatureFlag.Tests/CommunityToolkit.Aspire.GoFeatureFlag.Tests.csproj" />
292299
<Project Path="tests/CommunityToolkit.Aspire.Hosting.ActiveMQ.Tests/CommunityToolkit.Aspire.Hosting.ActiveMQ.Tests.csproj" />
293300
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Adminer.Tests/CommunityToolkit.Aspire.Hosting.Adminer.Tests.csproj" />
294301
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder.Tests/CommunityToolkit.Aspire.Hosting.Azure.DataApiBuilder.Tests.csproj" />
295302
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Azure.Extensions.Tests/CommunityToolkit.Aspire.Hosting.Azure.Extensions.Tests.csproj" />
303+
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.Tests/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.Tests.csproj" />
296304
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Bun.Tests/CommunityToolkit.Aspire.Hosting.Bun.Tests.csproj" />
297305
<Project Path="tests/CommunityToolkit.Aspire.Hosting.DbGate.Tests/CommunityToolkit.Aspire.Hosting.DbGate.Tests.csproj" />
298306
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Dbx.Tests/CommunityToolkit.Aspire.Hosting.Dbx.Tests.csproj" />

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<ItemGroup Label="Aspire Packages">
66
<!-- Aspire packages -->
77
<PackageVersion Include="Aspire.Hosting" Version="$(AspireVersion)" />
8+
<PackageVersion Include="Aspire.Hosting.Docker" Version="$(AspireVersion)" />
89
<PackageVersion Include="Aspire.Hosting.Azure.Storage" Version="$(AspireVersion)" />
910
<PackageVersion Include="Aspire.Hosting.Dapr" Version="$(AspireVersion)" />
1011
<PackageVersion Include="Aspire.Hosting.Azure.AppContainers" Version="$(AspireVersion)" />
@@ -74,6 +75,7 @@
7475
<!-- External packages -->
7576
<PackageVersion Include="AWSSDK.S3" Version="4.0.23.3" />
7677
<PackageVersion Include="Azure.Provisioning.AppContainers" Version="1.2.0" />
78+
<PackageVersion Include="Bitwarden.Secrets.Sdk" Version="1.0.0" />
7779
<PackageVersion Include="JsonSchema.Net" Version="7.4.0" />
7880
<PackageVersion Include="OllamaSharp" Version="5.4.12" />
7981
<PackageVersion Include="OpenFeature.Providers.GOFeatureFlag" Version="1.0.0" />

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ This repository contains the source code for the Aspire Community Toolkit, a col
2626
| - **Learn More**: [`Hosting.SqlDatabaseProjects`][sql-database-projects-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects][sql-database-projects-shields]][sql-database-projects-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects][sql-database-projects-shields-preview]][sql-database-projects-nuget-preview] | A hosting integration for the SQL Databases Projects. |
2727
| - **Learn More**: [`Hosting.Rust`][rust-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Hosting.Rust][rust-shields]][rust-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.Rust][rust-shields-preview]][rust-nuget-preview] | A hosting integration for the Rust apps. |
2828
| - **Learn More**: [`Hosting.Bun`][bun-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Hosting.Bun][bun-shields]][bun-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.Bun][bun-shields-preview]][bun-nuget-preview] | **Deprecated**: use the core `Aspire.Hosting.JavaScript` `AddBunApp(...)` integration. |
29+
| - **Learn More**: [`Hosting.Bitwarden.SecretManager`][bitwarden-secret-manager-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager][bitwarden-secret-manager-hosting-shields]][bitwarden-secret-manager-hosting-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager][bitwarden-secret-manager-hosting-shields-preview]][bitwarden-secret-manager-hosting-nuget-preview] | A hosting integration for Bitwarden Secrets Manager projects and managed secrets. |
30+
| - **Learn More**: [`Bitwarden.SecretManager`][bitwarden-secret-manager-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Bitwarden.SecretManager][bitwarden-secret-manager-client-shields]][bitwarden-secret-manager-client-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Bitwarden.SecretManager][bitwarden-secret-manager-client-shields-preview]][bitwarden-secret-manager-client-nuget-preview] | A client integration for authenticating and using the Bitwarden Secrets Manager SDK from Aspire applications. |
2931
| - **Learn More**: [`Hosting.Perl`][perl-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Hosting.Perl][perl-shields]][perl-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.Perl][perl-shields-preview]][perl-nuget-preview] | A hosting integration for Perl scripts and APIs. |
3032
| - **Learn More**: [`Hosting.Python.Extensions`][python-ext-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Python.Extensions][python-ext-shields]][python-ext-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.Python.Extensions][python-ext-shields-preview]][python-ext-nuget-preview] | An integration that contains some additional extensions for running python applications |
3133
| - **Learn More**: [`Hosting.KurrentDB`][kurrentdb-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Hosting.KurrentDB][kurrentdb-shields]][kurrentdb-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.KurrentDB][kurrentdb-shields-preview]][kurrentdb-nuget-preview] | An Aspire hosting integration leveraging the [KurrentDB](https://www.kurrent.io) container. |
@@ -155,6 +157,15 @@ This project is supported by the [.NET Foundation](https://dotnetfoundation.org)
155157
[bun-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Bun/
156158
[bun-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.Bun?label=nuget%20(preview)
157159
[bun-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Bun/absoluteLatest
160+
[bitwarden-secret-manager-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-bitwarden-secret-manager
161+
[bitwarden-secret-manager-hosting-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager
162+
[bitwarden-secret-manager-hosting-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/
163+
[bitwarden-secret-manager-hosting-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager?label=nuget%20(preview)
164+
[bitwarden-secret-manager-hosting-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager/absoluteLatest
165+
[bitwarden-secret-manager-client-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Bitwarden.SecretManager
166+
[bitwarden-secret-manager-client-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Bitwarden.SecretManager/
167+
[bitwarden-secret-manager-client-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Bitwarden.SecretManager?label=nuget%20(preview)
168+
[bitwarden-secret-manager-client-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Bitwarden.SecretManager/absoluteLatest
158169
[perl-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-perl
159170
[perl-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.Perl
160171
[perl-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Perl/
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
aspire-output
2+
.bitwarden/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<Nullable>enable</Nullable>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<ProjectReference Include="..\..\..\src\CommunityToolkit.Aspire.Bitwarden.SecretManager\CommunityToolkit.Aspire.Bitwarden.SecretManager.csproj" />
10+
</ItemGroup>
11+
12+
</Project>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Bitwarden.Sdk;
2+
using CommunityToolkit.Aspire.Bitwarden.SecretManager;
3+
using Microsoft.AspNetCore.Mvc;
4+
5+
var builder = WebApplication.CreateBuilder(args);
6+
7+
// Register the Bitwarden secret manager client with Aspire configuration binding.
8+
// Use the same connection name as the Bitwarden project in the AppHost
9+
// Configuration is injected as Aspire:Bitwarden:SecretManager:{connection_name}:{setting}
10+
// (e.g. Aspire:Bitwarden:SecretManager:secrets:AccessToken).
11+
builder.AddBitwardenSecretManagerClient(connectionName: "secrets", settings =>
12+
{
13+
// You can optionally override Aspire injected values here or set additional client settings.
14+
settings.IdentityUrl = "https://vault.bitwarden.com/identity";
15+
settings.ApiUrl = "https://vault.bitwarden.com/api";
16+
settings.DisableHealthChecks = true;
17+
});
18+
19+
var app = builder.Build();
20+
21+
app.MapGet("/", ([FromQuery] string? apiKey, BitwardenClient client, BitwardenSecretManagerClientSettings settings, IConfiguration configuration) =>
22+
{
23+
Guid secretId = configuration.GetValue<Guid>("DEMO_API_KEY_SECRET_ID");
24+
SecretResponse secret = client.Secrets.Get(secretId);
25+
if (string.IsNullOrEmpty(apiKey))
26+
{
27+
return Results.Problem("Missing apiKey query parameter.", statusCode: StatusCodes.Status401Unauthorized);
28+
}
29+
else if (secret.Value != apiKey)
30+
{
31+
return Results.Problem("Invalid apiKey.", statusCode: StatusCodes.Status401Unauthorized);
32+
}
33+
34+
return Results.Text("""
35+
Access granted to protected resource!
36+
37+
But please don't use query parameters for API keys in real applications... this is just a demo!
38+
Consider using an HTTP header or similar approach to keep secrets out of URLs and logs.
39+
""");
40+
});
41+
42+
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
43+
44+
app.Run();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"$schema": "https://json.schemastore.org/launchsettings.json",
3+
"profiles": {
4+
"http": {
5+
"commandName": "Project",
6+
"dotnetRunMessages": true,
7+
"launchBrowser": false,
8+
"applicationUrl": "http://localhost:5278",
9+
"environmentVariables": {
10+
"ASPNETCORE_ENVIRONMENT": "Development"
11+
}
12+
},
13+
"https": {
14+
"commandName": "Project",
15+
"dotnetRunMessages": true,
16+
"launchBrowser": false,
17+
"applicationUrl": "https://localhost:7298;http://localhost:5278",
18+
"environmentVariables": {
19+
"ASPNETCORE_ENVIRONMENT": "Development"
20+
}
21+
}
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Aspire.AppHost.Sdk/13.4.3">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsAspireHost>true</IsAspireHost>
8+
<UserSecretsId>632cd204-f3fe-4c77-98e1-fa65b87b5fa9</UserSecretsId>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Aspire.Hosting.Docker" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\..\..\src\CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager\CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.csproj" IsAspireProjectResource="false" />
17+
<ProjectReference Include="..\CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.ApiService\CommunityToolkit.Aspire.Hosting.Bitwarden.SecretManager.ApiService.csproj" />
18+
</ItemGroup>
19+
20+
</Project>

0 commit comments

Comments
 (0)