Skip to content
Open
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
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.

Remember to use the standardized port here

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.

Updated to 6543 in ff244d8.

```

## 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) ---
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.

Why is example 3 commented out?

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.

Uncommented and fully wired up — the example now registers AddMyVaultSecrets() and maps a /typed-secrets endpoint that injects the source-generated IMyVaultSecrets. Fixed in ff244d8.

// 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
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.

Perfect

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
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.

We should mark as many things internal and use InternalsVisibleTo as possible to narrow the public API

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.

Made WellKnownSecrets internal in ff244d8. The generator already uses its own string constants for attribute lookup, so no InternalsVisibleTo is needed.

{
/// <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