Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion .github/workflows/sdk_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,8 @@ jobs:
files=$(ls packages/*.nupkg | jq -R -s -c '
split("\n")[:-1]
| map(select(
(test("/Dapr\\.Workflow\\.Versioning\\.(Abstractions|Generators|Runtime)\\.")) | not
((test("/Dapr\\.Workflow\\.Versioning\\.(Abstractions|Generators|Runtime)\\.")) | not) and
((test("/Dapr\\.SecretsManagement\\.(Abstractions|Generators|Runtime)\\.")) | not)
))
')
echo "matrix=$files" >> $GITHUB_OUTPUT
Expand Down
986 changes: 946 additions & 40 deletions all.sln

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions examples/SecretManagement/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Dapr Secrets Management Sample

This sample demonstrates how to use the Dapr Secrets Management SDK to retrieve secrets from Dapr secret store components.

## Features Demonstrated

1. **Direct secret retrieval** — Using `DaprSecretsManagementClient` to fetch individual or bulk secrets via gRPC.
2. **Typed secret stores** — Using the `[SecretStore]` and `[Secret]` attributes with the source generator to create strongly-typed secret accessors.
3. **Dependency injection** — Registering the secrets client and typed stores via `IServiceCollection` extensions.

## Prerequisites

- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
- A configured Dapr secret store component (e.g., local file, Kubernetes secrets, Azure Key Vault)

## Running the Sample

```bash
dapr run --app-id secret-sample --app-port 5234 -- dotnet run
Comment thread
WhitWaldo marked this conversation as resolved.
Outdated
```

## Endpoints

| Method | Path | Description |
|--------|------|-------------|
| GET | `/secrets/{storeName}/{key}` | Retrieve a single secret by key |
| GET | `/secrets/{storeName}` | Retrieve all secrets from a store |

## NuGet Package Note

When consuming from NuGet, install the single **`Dapr.SecretsManagement`** package. The sub-projects (`Abstractions`, `Runtime`, `Generators`) are bundled into this one package and are not published individually.

```xml
<PackageReference Include="Dapr.SecretsManagement" Version="<version>" />
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using Dapr.SecretsManagement.Abstractions;

namespace SecretManagementSample;

/// <summary>
/// Example of a typed secret store interface. Apply the <see cref="SecretStoreAttribute"/> to an interface
/// and the Dapr Secrets Management source generator will produce:
/// 1. A concrete implementation that caches secrets loaded at startup.
/// 2. A DI registration extension method (e.g., <c>AddMyVaultSecrets()</c>).
///
/// Properties without <see cref="SecretAttribute"/> use the property name as the secret key.
/// Properties with <see cref="SecretAttribute"/> use the specified secret name.
/// </summary>
[SecretStore("my-vault")]
public partial interface IMyVaultSecrets
{
/// <summary>
/// The database connection string, retrieved from the "db-connection-string" secret key.
/// </summary>
[Secret("db-connection-string")]
string DatabaseConnection { get; }

/// <summary>
/// The API key. Uses the property name "ApiKey" as the secret key.
/// </summary>
string ApiKey { get; }
}
66 changes: 66 additions & 0 deletions examples/SecretManagement/SecretManagementSample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using Dapr.SecretsManagement;
using Dapr.SecretsManagement.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Register the Dapr Secrets Management client with dependency injection.
// This makes DaprSecretsManagementClient available throughout the application.
builder.Services.AddDaprSecretsManagementClient();

// If you've defined a typed secret store interface with [SecretStore] and the source generator,
// you can register it here. For example:
//
// builder.Services.AddDaprSecretsManagementClient()
// .AddMyVaultSecrets(); // Generated extension method
//
// See IMyVaultSecrets.cs for the typed secret store interface definition.

var app = builder.Build();

// --- Example 1: Direct secret retrieval ---
app.MapGet("/secrets/{storeName}/{key}", async (
string storeName,
string key,
DaprSecretsManagementClient secretsClient,
CancellationToken cancellationToken) =>
{
var secret = await secretsClient.GetSecretAsync(storeName, key, cancellationToken: cancellationToken);
return Results.Ok(secret);
});

// --- Example 2: Bulk secret retrieval ---
app.MapGet("/secrets/{storeName}", async (
string storeName,
DaprSecretsManagementClient secretsClient,
CancellationToken cancellationToken) =>
{
var secrets = await secretsClient.GetBulkSecretAsync(storeName, cancellationToken: cancellationToken);
return Results.Ok(secrets);
});

// --- Example 3: Using typed secret store (source-generated) ---
Comment thread
WhitWaldo marked this conversation as resolved.
Outdated
// If you have registered a typed secret store (see IMyVaultSecrets.cs), you can inject it directly:
//
// app.MapGet("/typed-secrets", (IMyVaultSecrets secrets) =>
// {
// return Results.Ok(new
// {
// DatabaseConnection = secrets.DatabaseConnection,
// ApiKey = secrets.ApiKey
// });
// });

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"profiles": {
"SecretManagementSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5234",

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.

Can you ensure that the port is consistent across other examples? I know they're all over the place today, but I'm ok standardizing on something easy like 6543 so all the READMEs can use a consistent port across all examples.

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.

Standardized to port 6543 in both launchSettings.json and README.md in ff244d8.

"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>

<!-- Added for demonstration purposes - emit generated source files to disk for inspection -->
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

<!--
NOTE: When consuming from NuGet, use the single 'Dapr.SecretsManagement' package instead of
Comment thread
WhitWaldo marked this conversation as resolved.
these individual project references. The sub-projects (Abstractions, Runtime, Generators) are
NOT published to NuGet individually — they are bundled into the Dapr.SecretsManagement package.

Replace the ProjectReference items below with:
<PackageReference Include="Dapr.SecretsManagement" Version="<version>" />
-->
<ItemGroup>
<ProjectReference Include="..\..\..\src\Dapr.SecretsManagement.Abstractions\Dapr.SecretsManagement.Abstractions.csproj" />
<ProjectReference Include="..\..\..\src\Dapr.SecretsManagement.Runtime\Dapr.SecretsManagement.Runtime.csproj" />
<ProjectReference Include="..\..\..\src\Dapr.SecretsManagement.Generators\Dapr.SecretsManagement.Generators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>
2 changes: 2 additions & 0 deletions src/Dapr.Common/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
[assembly: InternalsVisibleTo("Dapr.Extensions.Configuration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Workflow, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Workflow.Grpc, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.SecretsManagement.Runtime, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]

[assembly: InternalsVisibleTo("Dapr.Actors.AspNetCore.IntegrationTest, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Actors.AspNetCore.IntegrationTest.App, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
Expand All @@ -49,3 +50,4 @@
[assembly: InternalsVisibleTo("Dapr.Extensions.Configuration.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Jobs.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Messaging.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.SecretsManagement.Runtime.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
56 changes: 56 additions & 0 deletions src/Dapr.SecretsManagement.Abstractions/SecretAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.SecretsManagement.Abstractions;

/// <summary>
/// Specifies the secret key name used when retrieving the value of the annotated property from a Dapr
/// secret store. When this attribute is omitted, the property name is used as the secret key.
/// </summary>
/// <remarks>
/// This attribute should be applied to properties on an interface that is also annotated with
/// <see cref="SecretStoreAttribute"/>. The source generator uses this metadata to map each property
/// to the correct secret key during bulk secret retrieval.
/// </remarks>
/// <example>
/// <code>
/// [SecretStore("my-vault")]
/// public partial interface IMySecrets
/// {
/// [Secret("database-connection-string")]
/// string DbConnection { get; }
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class SecretAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="SecretAttribute"/> class.
/// </summary>
/// <param name="secretName">
/// The name of the secret key in the Dapr secret store. This value is used when calling the
/// Dapr Secrets API to retrieve the secret value.
/// </param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="secretName"/> is <see langword="null"/>.</exception>
public SecretAttribute(string secretName)
{
ArgumentNullException.ThrowIfNull(secretName);
SecretName = secretName;
}

/// <summary>
/// Gets the name of the secret key in the Dapr secret store.
/// </summary>
public string SecretName { get; }
}
68 changes: 68 additions & 0 deletions src/Dapr.SecretsManagement.Abstractions/SecretStoreAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.SecretsManagement.Abstractions;

/// <summary>
/// Marks an interface as a typed accessor for a Dapr secret store. When applied to a <c>partial interface</c>,
/// the Dapr Secrets Management source generator will produce a concrete implementation that retrieves secrets
/// from the specified Dapr secret store component and registers it in the dependency injection container.
/// </summary>
/// <remarks>
/// <para>
/// The interface should declare <see langword="string"/>-typed read-only properties. Each property maps to a
/// single secret key in the store. The key name defaults to the property name but can be overridden with
/// <see cref="SecretAttribute"/>.
/// </para>
/// <para>
/// Generated implementations load all mapped secrets in bulk at startup via an <c>IHostedService</c> and
/// expose them as synchronous properties. This makes secrets available immediately after host startup without
/// requiring callers to manage async flows.
/// </para>
/// <example>
/// <code>
/// [SecretStore("my-vault")]
/// public partial interface IMySecrets
/// {
/// /// &lt;summary&gt;The database connection string.&lt;/summary&gt;
/// [Secret("db-connection-string")]
/// string DatabaseConnection { get; }
///
/// /// &lt;summary&gt;The API key (uses property name as secret key).&lt;/summary&gt;
/// string ApiKey { get; }
/// }
/// </code>
/// </example>
/// </remarks>
[AttributeUsage(AttributeTargets.Interface, Inherited = false, AllowMultiple = false)]
public sealed class SecretStoreAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="SecretStoreAttribute"/> class.
/// </summary>
/// <param name="storeName">
/// The name of the Dapr secret store component to retrieve secrets from. This must match the
/// <c>metadata.name</c> of a configured Dapr secret store component.
/// </param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="storeName"/> is <see langword="null"/>.</exception>
public SecretStoreAttribute(string storeName)
{
ArgumentNullException.ThrowIfNull(storeName);
StoreName = storeName;
}

/// <summary>
/// Gets the name of the Dapr secret store component that secrets will be retrieved from.
/// </summary>
public string StoreName { get; }
}
30 changes: 30 additions & 0 deletions src/Dapr.SecretsManagement.Abstractions/WellKnownSecrets.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// ------------------------------------------------------------------------
// Copyright 2026 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

namespace Dapr.SecretsManagement.Abstractions;

/// <summary>
/// Well-known names and constants related to Dapr Secrets Management.
/// </summary>
public static class WellKnownSecrets
Comment thread
WhitWaldo marked this conversation as resolved.
Outdated
{
/// <summary>
/// The fully qualified name of <see cref="SecretStoreAttribute"/>.
/// </summary>
public const string SecretStoreAttributeFullName = "Dapr.SecretsManagement.Abstractions.SecretStoreAttribute";

/// <summary>
/// The fully qualified name of <see cref="SecretAttribute"/>.
/// </summary>
public const string SecretAttributeFullName = "Dapr.SecretsManagement.Abstractions.SecretAttribute";
}
Loading