man-group / dapr-sidekick-dotnet

Dapr Sidekick for .NET - a lightweight lifetime management component for Dapr
Apache License 2.0
175 stars 21 forks source link

Can I use sidekick to access secrets? #48

Closed danhaywood closed 1 year ago

danhaywood commented 1 year ago

Ask your question here

Seem to have a catch-22 on this.

For dev, I'd like to use sidekick, but ulltimately in prod the app will run under k8s. In prod, though, the idea is that the app will query secrets to obtain its connection strings etc, and then set up its DB context etc. However, this stuff has to be done before a[[ = builder.Build();

My issue though is that daprd isn't started until after app.Run(), and I can't get my secrets until it is running, but I need my secrets to build the DB context, and yet that needs to be done before builder.Build()

app = builder.Build() < app.Run() < daprd.exe < query for secrets < build connection string < create DB context < app = builder.Build()

Hopefully I'm being very dumb?

badgeratu commented 1 year ago

In k8s I agree daprd would normally be running in your pod alongside your application and would likely be available at the time you are building the DI container. However to query Dapr for secrets you would normally use the Dapr Client from the .NET SDK, which is only available from the DI container after you've invoked builder.Build(). You could use an alternative means of querying the Dapr HTTP/gRPC API directly before DI is constructed, but then you wouldn't easily be able to take advantage of API tokens etc for securing communication with the sidecar. Alternatively, you could create a DB context factory that you register with DI that constructs a DB Context on demand, and in there ask Dapr for the secret (if not already retrieved) throwing an exception if it can't be retreieved or not available. It's a bit more resilient than asking for it only on startup just in case Dapr isn't available (or has gone away) at any time - which is possible being an external process.

danhaywood commented 1 year ago

Hi Simon, thanks for that, confirms my understanding.

I want the same general flow for dev (under sidekick) and prod (under k8s), with only the dapr implementation of the secretstore changing in between. Because with sidekick the sidecar will only be available after startup, I think that means I need to go with the lazily created DB Context Factory.

So my follow on isn't really to do with sidekick but on the mechanics of creating that DB Context Factory lazily (cos I'm more a java guy than .NET ).... is this something you've done / could point me to a useful blog on? Otherwise I'll hit StackOverflow.

cheers Dan

PS: By the way, thanks for the great work on sidekick, should've said

ijn-kruso commented 1 year ago

I'm currently running into the same issue where I'm fetching secrets from Azure Key Vault to set some of my app settings. builder.Configuration.AddDaprSecretStore("secretstore", daprClient);

I ended up going through the azure api instead as the SideCar wasn't running at this point during setup. Maybe there is an alternative way of launching the DaprSideCar, than a hosted service, or manually start the hosted service before builder.build()?

danhaywood commented 1 year ago

That sounds like it would solve it ... though I saw a PR applied which was to move the starting into a hosted service and therefore after App.Run(), so perhaps there's a good reason for the current design.

danhaywood commented 1 year ago

OK, to answer my own question, I defined my Db context like this

public class MovieDbContext : DbContext
{
    private readonly IConnectionStringService _connectionStringService;

    public MovieDbContext (DbContextOptions<MovieDbContext> options, IConnectionStringService connectionStringService)
        : base(options)
    {
        _connectionStringService = connectionStringService;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseLazyLoadingProxies()
            .UseSqlServer(_connectionStringService.ConnectionString);
    }
   ...
}

where

public interface IConnectionStringService
{
    public string ConnectionString { get; }
}

implemented by

public class ConnectionStringServiceUsingDaprSecrets : IConnectionStringService
{
    public ConnectionStringServiceUsingDaprSecrets()
    {
        var daprClient = new DaprClientBuilder().Build();

        // might be worth wrapping with Polly for retry ?
        var secretAsync = daprClient.GetSecretAsync("movie-secret-store", "ConnectionString");
        var connectionString = secretAsync.Result["ConnectionString"];

        ConnectionString = connectionString;
    }

    public string ConnectionString { get; }
}

and registered with:

builder.Services.AddSingleton<IConnectionStringService, ConnectionStringServiceUsingDaprSecrets>();