Git Product home page Git Product logo

awssecretsmanagerconfigurationextensions's Introduction

Build status NuGet version

This repository contains a provider for Microsoft.Extensions.Configuration that retrieves secrets stored in AWS Secrets Manager.

Overview

Every application has some kind of setting that should never be checked into the source control like a database connection string or some external API credentials. Yet, your application needs that setting to be able to properly perform its job.

.NET Core natively supports the ingestion of settings from different sources. This allows the customization of the application according to the current environment. The typical example is the connection string to a database that can vary so that each environment can connect to a specific database.

Developers working on .NET Core often take advantage of the secret manager for their development environment. On the other hand, settings for the production environment are often stored in environment variables.

AWS Secrets Manager offers a serverless managed solution to the problem.

Kralizek.Extensions.Configuration.AWSSecretsManager offers an convenient method to access your secrets stored in AWS Secrets Manager.

This is how your ASP.NET Core 2.0 application will look like. Notice the config.AddSecretsManager(); in the delegate passed to the ConfigureAppConfiguration method.

public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((hostingContext, config) =>  
                {
                    config.AddSecretsManager();
                })
                .UseStartup<Startup>()
                .Build();
}

This code is also available in this sample.

You can use AddSecretsManager also in a classic console application.

static void Main(string[] args)
{
    var builder = new ConfigurationBuilder();
    builder.AddSecretsManager();

    var configuration = builder.Build();

    Console.WriteLine("Hello World!");
}

This code is also available in this sample.

Note: the snippets above assume that some AWS credentials are available by default to your application. Here you can see how to setup your environment.

Mentions by the community

Amazon Elastic Kubernetes Service (EKS)

In order to authenticate requests to AWS Secret Manager a pod needs to use a IAM role that grants access to your secrets. Amazon introduced IAM roles for service accounts to make this possible without third party solutions.

However, this feature requires an additional package to be installed which is loaded by reflection.

dotnet add AWSSDK.SecurityToken

Customization

This library offers the possibility to customize how the setting values are retrieved from AWS Secrets Manager and added to the Configuration provider.

AWS Credentials

By default, this library let the AWS SDK decide which credentials should be used according to available settings. You can customize that by providing your own set of credentials.

Here are some samples.

Basic credentials

You can provide your AWS access and secret key directly by using the BasicAWSCredentials class.

Note You should avoid this. After all, the intent of this library is to remove our secrets from the source code.

var credentials = new BasicAWSCredentials("my-accessKey", "my-secretKey");
builder.AddSecretsManager(credentials: credentials);

Using credentials connected to a profile (old)

You can use a specific profile by using the StoredProfileAWSCredentials class.

Note The StoredProfileAWSCredentials has been marked as obsolete and will be removed in a later release.

var credentials = new StoredProfileAWSCredentials("my_profile_name");
builder.AddSecretsManager(credentials: credentials);

Using credentials connected to a profile (current)

You can use the CredentialProfileStoreChain class to fetch a profile from the different sources available.

var chain = new Amazon.Runtime.CredentialManagement.CredentialProfileStoreChain();

if (chain.TryGetAWSCredentials("my_profile_name", out var credentials))
{
    builder.AddSecretsManager(credentials);
}

You can see an example here.

AWS Region

By default, this library fetches the secrets registered in the AWS region associated with the default profile. You can change that by passing the desired region.

builder.AddSecretsManager(region: RegionEndpoint.EUWest1);

You can see an example here.

Filtering secrets before they are retrieved

Best practices suggest that you use IAM roles to restrict the list of secrets that your application has access to. This is not always doable, especially in older setups (e.g. multiple applications sitting on the same EC2 instance sharing the permissions via EC2 instance profile).

In this case, it's still possible to restrict which secrets should be retrieved by your application by providing a predicate to be applied on each secret returned.

Note Retrieving the list of available secrets and their secret value happens in two different moments so you can prevent your application from ever accessing the value of the secrets you don't need.

var acceptedARNs = new[]
{
    "MySecretARN1",
    "MySecretARN2",
    "MySecretARN3",
};

builder.AddSecretsManager(configurator: options =>
{
    options.SecretFilter = entry => acceptedARNs.Contains(entry.ARN);
});

You can see an example here.

Defining list of secrets in advance (no list secrets permission required)

Security best practices sometimes prevent the listing of secrets in a production environment. As a result, it is possible to define a list of secrets in lieu of a secret filter. When using this this approach, the library will only retrieve the secrets whose ARN or name are present in the given AcceptedSecretArns list.

var acceptedARNs = new[]
{
    "MySecretFullARN-abcxyz",
    "MySecretPartialARN",
    "MySecretUniqueName",
};

builder.AddSecretsManager(configurator: options =>
{
    options.AcceptedSecretArns = acceptedARNs;
});

Altering how the values are added to the Configuration

Sometimes we are not in control of the full system. Maybe we are forced to use secrets defined by someone else that uses a different convention.

In this case, you can provide a function that gets invoked every time a value is discovered. This function allows you to customize which key should be used.

As an example, here we are converting all incoming keys to upper case

builder.AddSecretsManager(configurator: options =>
{
    options.KeyGenerator = (entry, key) => key.ToUpper();
});

You can see an example here.

Customizing the GetSecretValueRequest

Sometimes we may want to request a different version of the secret or be required to specify the version stage.

In this case, you can provide a function that updates the GetSecretValueRequest that is used to retrieve the secret from AWS before the request is sent.

As an example, here we are adding a VersionStage of "AWSCURRENT" to every GetSecretValueRequest.

builder.AddSecretsManager(configurator: options =>
{
    options.ConfigureSecretValueRequest = (request, context) => request.VersionStage = "AWSCURRENT";
});

Customizing the AmazonSecretsManagerConfig, for example to use localstack

There are some situations where you might want to customize how the AmazonSecretsManagerConfig is built, for example when you want to use localstack during local development. In those cases, you should customize the ServiceUrl.

builder.AddSecretsManager(configurator: options =>
{
    options.ConfigureSecretsManagerConfig = c => {
        c.ServiceUrl = "http://localhost:4584" // The url that's used by localstack
    };
});

Versioning

This library follows Semantic Versioning 2.0.0 for the public releases (published to the nuget.org).

How to build

This project uses Cake as a build engine.

If you would like to build this project locally, just execute the build.cake script.

You can do it by using the .NET tool created by CAKE authors and use it to execute the build script.

dotnet tool install -g Cake.Tool
dotnet cake

Stargazers over time

Stargazers over time

awssecretsmanagerconfigurationextensions's People

Contributors

andrewlock avatar avalara-stephen-hickey avatar dependabot[bot] avatar devklick avatar erwinvandervalk avatar jabberwik avatar jcanady20 avatar jchannon avatar kipters avatar kralizek avatar manuel-guilbault avatar oliverrc avatar rgmills avatar scott-phillips-ah avatar therubble avatar udlose avatar ycherkes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

awssecretsmanagerconfigurationextensions's Issues

Reloading configuration without polling

I've got a use case where I need to reload configuration but don't wish to use polling (In a particular sensitive code base where performance etc. is everything).

SecretsManagerConfigurationProvider.cs

public Task ForceReloadAsync(CancellationToken cancellationToken)
{
    return ReloadAsync(cancellationToken);
}

Would you have an objections if I raised this as a PR? I chose to go down adding the new method rather than opening up the access to the existing method so I'd clearly show the intent.

The code is using ListSecrets even though I have both defined AcceptedSecretArns and SecretsFilter

When deployed to AWS, it fails calling

Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.FetchAllSecretsAsync(CancellationToken cancellationToken)

With error

---> Amazon.SecretsManager.AmazonSecretsManagerException: User: arn:aws:sts::*REDACTED is not authorized to perform: secretsmanager:ListSecrets because no identity-based policy allows the secretsmanager:ListSecrets action

How do I get it to only get the requested secrets?

I am using partial ARNs (just the unique keynames - is that why?

Flatten database secrets to ConnectionString entry?

I am sorry if this has been covered already. I cannot find anything like it in discussion.

AWS database secrets are dictionaries. Say I have specified a DB secret for an application called MyApplication:
{"username":"myuser","password":"mypassword","engine":"sqlserver","host":"mysqlserver","port":"1433","dbname":"mysqldb"}

I would like to convert this to a ConnectionString entry in my configuration, like this:
"ConnectionStrings" { "MyApplicationConnectionString": "Data Source=mysqlserver;Database=mysqlserver;Integrated Security=false;User ID=myuser;Password=mypassword;" }

But the configuration builder treats each item in the dictionary as a single configuration entry. So there is one entry for "username," another for "password," and so on. And no way I can find to flatten them into a single entry.

I considered using KeyGenerator but that only will allow mapping from each individual entry in the secret (e.g. "username," "password," etc.) to new entries in the destination config space, one by one.

Secrets that look like Json Arrays or Objects (but are not) do not work

Secrets that look like arrays or objects (start with "[" or "{"), but are not actually Json do not work. e.g. a secret being "{THIS IS NOT AN OBJECT" or "[THIS IS NOT AN ARRAY" do not work. There is a function in the source checking for the value starting with these called IsJson, which is probably too simplistic a check (maybe JToken.Parse and check for JsonReaderException instead)?

Upgrade to latest SDK

Is it possible to upgrade this package to use the latest AWSSDK packages?

We have upgraded to the latest versions and now have a lot of build warning because of the upper constraint of version 3.4 on AWSSDK.Core.

Possibility to customize the secret's key

As suggested by @rgmills in #1, the idea is to offer an extension point that allows the user of this library to customize the key before adding the secret to the collection of configuration values available to bind.

Unable to parse nested array

Hi @Kralizek ,

Just trying to set a config in aws secrets manager like below:

{
  "HealthChecks-UI": {
    "HealthChecks": [
      {
        "Name": "Selz Databases",
        "Uri": "http://127.0.0.1:44444/healthz-databases"
      },
      {
        "Name": "Selz Services",
        "Uri": "http://127.0.0.1:44444/healthz-services"
      },
      {
        "Name": "Selz Endpoints",
        "Uri": "http://127.0.0.1:44444/healthz-endpoints"
      },
      {
        "Name": "S3 Buckets",
        "Uri": "http://127.0.0.1:44444/healthz-buckets"
      }
    ]
  }
}

It throws exception:

$exception    {System.InvalidCastException: Unable to cast object of type 'Newtonsoft.Json.Linq.JObject' to type 'Newtonsoft.Json.Linq.JProperty'.
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.ExtractValues(JToken token, String prefix)+MoveNext()
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.ExtractValues(JToken token, String prefix)+MoveNext()
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.ExtractValues(JToken token, String prefix)+MoveNext()
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.LoadAsync()
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.Load()
   at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
   at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.BuildCommonServices(AggregateException& hostingStartupErrors)
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at Selz.HealthChecks.Web.Program.Main(String[] args) in D:\Source\selz\Services.HealthChecks\src\Selz.HealthChecks.Web\Program.cs:line 16}    System.InvalidCastException

Can you have a simple test that it seems can't parse nested json array correctly?

Thank you

Broken tests when executed locally

Tests in SecretsManagerConfigurationSourceTests are broken when executed locally.

They work in CI/CD because we have custom environment variables set by AppVeyor

environment:
AWS_ACCESS_KEY_ID: for_testing
AWS_SECRET_ACCESS_KEY: for_testing
AWS_REGION: us-east-1

Developers should be able to execute tests and the full build chain on their local machines.

Take AWS options from appsettings

This is a nice little library, but one issue that I'm having is that it doesnt take AWS options from appsettings. What are your thoughts on supporting this?

For example, if I have the following in my appsettings file, I'd want this information to be used when creating the Secrets Manager client:

{
    "AWS": {
        "Region": "eu-west-1",
        "Profile": "dev",
    }
}

At the moment, it only seems to use the information in the credentials file, or if the AWS_PROFILE or AWS_REGION environment variable are present, it'll use the information from there.

The AWS .NET Configuration Extension for Systems Manager has an AddSystemsManager method which builds the IConfigurationBuilder and fetches the AWSOptions from it by IConfiguration.GetAWSOptions(), which allows these options to come from appsettings. I'm wondering if it would be possible to support something similar in this library.

Is it possible to provide AcceptedArns through config file?

Hello,

I'm trying to decouple accepted ARNs list from the program.cs to a config file, I have my config options defined below in appsettings.json,

"SecretsManagerConfigurationProviderOptions": {
   "acceptedSecretArns": [ "my-arn" ]
 }

Then from the code, I just call the manager like below

 try
            {
                var config = builder.Build();
                logger?.Information("reading secure settings from secrets manager");
                builder.AddSecretsManager(region: RegionEndpoint.USWest2);
                config = builder.Build();
            }
            catch (Exception ex)
            {
                logger?.Error(ex, "Unable to configure SecretsManager");
            }
            return builder;

Here, I'm not sure if the accepdSecretArns would be passed down to the library since I don't have the environment to test AWS secrets manager. Could you share your thoughts if this is correct approach?
Also, I would like to make RegionEndpoint as configurable from the appsettings option, any thoughts if I can achieve this?

Thanks

How to use this library with newer versions of the AWS SDK

A very common issue being reported when trying to install this package in a project using packages of the AWS SDK newer than 3.3.

This issue is caused because the AWS SDK has strict version boundaries forcing the usage of packages within the same major version family (e.g. you can't mix the AWS S3 3.5 package with AWS EC2 3.3 package).

This library is built targeting the AWS SDK 3.3 but, unlike the AWS SDK, it doesn't have strict limits on the targeted AWS SDK version so it's possible to use this library in projects targeting the AWS SDK 3.4 or newer.

To do so, you can simply leverage the automatic assembly redirect built into the .NET SDK projects.

Normally your project file would look like

<ItemGroup>
 <PackageReference Include="Kralizek.Extensions.Configuration.AWSSecretsManager" Version="1.5.0" />
</ItemGroup>

Let's assume you wanted to add the latest package for S3, you would do

$> dotnet add package AWSSDK.S3

This would give you the following message

error: NU1107: Version conflict detected for AWSSDK.Core. Install/reference AWSSDK.Core 3.5.2.9 directly to project SecretsManagerSDK to resolve this issue.
error:  SecretsManagerSDK -> AWSSDK.S3 3.5.8.4 -> AWSSDK.Core (>= 3.5.2.9 && < 3.6.0)
error:  SecretsManagerSDK -> Kralizek.Extensions.Configuration.AWSSecretsManager 1.5.0 -> AWSSDK.SecretsManager 3.3.0 -> AWSSDK.Core (>= 3.3.21.20 && < 3.4.0).
info : Package 'AWSSDK.S3' is compatible with all the specified frameworks in project 'C:\Development\Tests\SecretsManagerSDK\SecretsManagerSDK.csproj'.
info : PackageReference for package 'AWSSDK.S3' version '3.5.8.4' updated in file 'C:\Development\Tests\SecretsManagerSDK\SecretsManagerSDK.csproj'.
info : Committing restore...
info : Writing assets file to disk. Path: C:\Development\Tests\SecretsManagerSDK\obj\project.assets.json
log  : Failed to restore C:\Development\Tests\SecretsManagerSDK\SecretsManagerSDK.csproj (in 233 ms).

To solve this issue, first install the latest version of AWSSDK.SecretsManager

$> dotnet add package AWSSDK.SecretsManager

Then, install the S3 package

$> dotnet add package AWSSDK.S3

Your project file will eventually look like this

<ItemGroup>
  <PackageReference Include="AWSSDK.S3" Version="3.5.8.4" />
  <PackageReference Include="AWSSDK.SecretsManager" Version="3.5.0.69" />
  <PackageReference Include="Kralizek.Extensions.Configuration.AWSSecretsManager" Version="1.5.0" />
</ItemGroup>

How to access secrets json and bind to a class?

If i have secret called Config and in it plain:

{
  "ConectionStrings": {

    "UserDb": "...."
  }
}
.ConfigureServices((hostContext, services) =>

  services.AddOptions<DataAccessOptions>().Bind(hostContext.Configuration);
)

Where DataAccessOptions is a class with:

public class DataAccessOptions
    {
        public ConfigOptions Config { get; set; }

        public class ConfigOptions
        {
            public DataAccessConnectionString ConnectionStrings { get; set; }
        }


        public class DataAccessConnectionString
        {
            public string UserDb { get; set; }

        }
}

As far as I can tell this does not work.

This does not work as well.

var options = new DataAccessOptions();
hostContext.Configuration.GetSection("Config").Bind(options);

this does not work
hostContext.Configuration.GetSection("Config");

Only thing that works is:

hostContext.Configuration.GetValue<string>("Config:ConnectionStrings:UserDb");

And that kind of is not a way. Imagine i got N of stuff in secret. Then I have to initiate class and fill in all of that stuff manually like:

var options = new DataAccessOptions {
     Config = new ConfigOptions {
        ConnectionStrings = new DataAccessConnectionString {
             UserDb = hostContext.Configuration.GetValue<string>("Config:ConnectionStrings:UserDb");
             // and N other stuff in here
        }
     }
    
}

Is there a way to bind secrets to a class and not to do that get one at the time.

On Azure when you define secret "Config:ConnectionStrings:UserDb" syntax is recognised like section on you can unpack in one line of code.

Discuss some additional configuration options

Hello!

I stumbled upon this library before building my own, but there were a few things I was looking for that aren't currently available and am curious if you'd be interested in discussing adding them.

  1. I don't want all secrets and values to be loaded, I'd like to either:
    • Provide a filter (i.e. Func<SecretListEntry, bool>)
    • Supply a list of ARNs of secrets I want to include
  2. I'd like the option to hook into composing the prefix that is applied for the key. For backing this into an existing application i.e. I have the Secret named prod/MyApp/MyService with a MyDatabase.ConnectionString property I'd like to exclude prod/MyApp/MyService: as part of the key

Thanks!

Docker-compose: Unable to retrieve credentials. Message = "Unable to reach credentials server"

I have a legacy app running on a EC2 instance (NOT using ECS or EKS) , which is running on docker-compose with an nginx proxy.

If I run the app locally, It runs properly without issues.

Im pulling the secrets using that library with this code:

configurationManager.AddSecretsManager(configurator: options =>
        {
            options.SecretFilter = entry => entry.Name.StartsWith($"{environmentName}_{applicationName}");
            options.KeyGenerator = (_, s) => s
                .Replace($"{environmentName}_{applicationName}_", string.Empty)
                .Replace("__", ":");
            options.PollingInterval = TimeSpan.FromSeconds(10);
        });

I added an IAM Role to the EC2 instance, and that role has SecretsManagerReadWrite permissions.

Using the cli on the instance, I can do:

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRoleName
And get a proper response

When we first ran the app, we WERE getting the following exception:

**Unhandled exception. Amazon.Runtime.AmazonServiceException: Unable to get IAM security credentials from EC2 Instance Metadata Service**.
   at Amazon.Runtime.DefaultInstanceProfileAWSCredentials.FetchCredentials()
   at Amazon.Runtime.DefaultInstanceProfileAWSCredentials.GetCredentials()
   at Amazon.Runtime.DefaultInstanceProfileAWSCredentials.GetCredentialsAsync()
   at Amazon.Runtime.Internal.CredentialsRetriever.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.ErrorCallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.MetricsHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.FetchAllSecretsAsync(CancellationToken cancellationToken)
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.FetchConfigurationAsync(CancellationToken cancellationToken)
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.LoadAsync()
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.Load()
   at Microsoft.Extensions.Configuration.ConfigurationManager.AddSource(IConfigurationSource source)
   at Microsoft.Extensions.Configuration.ConfigurationManager.Microsoft.Extensions.Configuration.IConfigurationBuilder.Add(IConfigurationSource source)
   at Microsoft.Extensions.Configuration.SecretsManagerExtensions.AddSecretsManager(IConfigurationBuilder configurationBuilder, AWSCredentials credentials, RegionEndpoint region, Action1 configurator)

I updated the docker-compose adding 2 env variables (AWS_REGION and AWS_CONTAINER_CREDENTIALS_RELATIVE_URI). And it started working (no idea why).

This is the docker-compose

services:
  nginx-proxy:
    image: custom-nginx:latest
    restart: always
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro


  production:
    image: production:latest
    restart: always
    environment:
      - AWS_REGION=us-east-2
      - AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/latest/meta-data/iam/security-credentials/MyRoleName

  staging:
    image: staging:latest
    restart: always
    environment:
      - AWS_REGION=us-east-2
      - AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/latest/meta-data/iam/security-credentials/MyRoleName

However, after updating the docker image and 'docker-compose up -d', it started failing again, but now with a different exception:

From the call stack, I can see that it is calling ECSTaskCredentials.GenerateNewCredentials(), but this is not running on ECS

2023-05-09 14:20:33.668 +00:00 [ERR] AWS ERROR: **Unable to retrieve credentials. Message = "Unable to reach credentials server"**.
Amazon.Runtime.AmazonServiceException: Unable to retrieve credentials. Message = "Unable to reach credentials server".
   at Amazon.Runtime.ECSTaskCredentials.GenerateNewCredentials()
   at Amazon.Runtime.RefreshingAWSCredentials.<GenerateNewCredentialsAsync>b__16_0()
   at System.Threading.Tasks.Task1.InnerInvoke()
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
   at Amazon.Runtime.RefreshingAWSCredentials.GetCredentialsAsync()
   at Amazon.Runtime.Internal.CredentialsRetriever.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.ErrorCallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.MetricsHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.FetchAllSecretsAsync(CancellationToken cancellationToken)
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.FetchConfigurationAsync(CancellationToken cancellationToken)
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.LoadAsync()
   at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.Load()
   at Microsoft.Extensions.Configuration.ConfigurationManager.AddSource(IConfigurationSource source)
   at Microsoft.Extensions.Configuration.ConfigurationManager.Microsoft.Extensions.Configuration.IConfigurationBuilder.Add(IConfigurationSource source)

Any help provided would be greatly appreciated beyond measure.

FR: Read just specified keys

Hi.

Thanks for great library.

We don't allow our applications in production to enumerate secrets in account. Instead we give them one or several full paths to secrets that app will need and it can access only them.

I can provide a PR, but I'm not sure how to approach this. E.g. we can provide something like AllowedKeys as a property in SecretsManagerConfigurationProviderOptions and if it's not empty just get only those keys instead of enumerating all of them.

Would you accept such a PR? Or, probably, you have better idea how to implement this feature?

Not merging options

I hope this is something simple I'm missing. It appears that the settings coming from Secrets Manager aren't merged under the same key from appsettings.json.

In my appsettings.json I have this
"JWT": { "ValidAudience": "http://localhost:4200", "ValidIssuer": "http://localhost:61955" },

In AWS Secrets Manager I've got a secret named "MyBrokerDev" that contains a number or key/values:
{ "Database__ConnectString": "...", "JWT__Secret": "ThisIsTheJWTSecret" }

my KeyGenerator does replace "___" with ":".

My JWTOptions class has "ValidAudience", "ValidIssuer" and "Secret" properties all as [Required] attributes . I validate those attributes when loaded. When loading from my Elastic Beanstalk environment, the validation is failing with "System.ComponentModel.DataAnnotations.ValidationException: The Secret field is required.".

I've added a dump of all the settings the ConfigureServices :

var root = (IConfigurationRoot)Configuration; logger.LogInformation($"Config: {root.GetDebugView()}");

Curiously, it renders as if there are two JWT sections:

Nov  9 17:01:33 ip-172-31-28-236 web: JWT:
Nov  9 17:01:33 ip-172-31-28-236 web: Secret=ThisIsTheJWT (SecretsManagerConfigurationProvider)
Nov  9 17:01:33 ip-172-31-28-236 web: JWT:
Nov  9 17:01:33 ip-172-31-28-236 web: ValidAudience=http://localhost:4200 (JsonConfigurationProvider for 'appsettings.json' (Optional))
Nov  9 17:01:33 ip-172-31-28-236 web: ValidIssuer=http://localhost:61955 (JsonConfigurationProvider for 'appsettings.json' (Optional))

locally, when I run this with an environment variable "JWT__Secret" = "ThisIsTheJWT", the same dump of settings appears to show them merged:

JWT:
   Secret=ThisIsTheJWTSecret (EnvironmentVariablesConfigurationProvider)
   ValidAudience=http://localhost:4200 (JsonConfigurationProvider for 'appsettings.Development.json' (Optional))
   ValidIssuer=http://localhost:61955 (JsonConfigurationProvider for 'appsettings.Development.json' (Optional))

Any ideas?

JsonReaderException if entire secret value is an array of json

If you create a Secret with a PlainText value that is an array of json, you will receive a JsonReaderException:

       Newtonsoft.Json.JsonReaderException: "Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path '', line 1, position 1."

This is caused by the use of JObject.Parse() on the given value before it is determined whether or not it is an array. The correct solution is to use JToken.Parse() instead.

Example:

[{
  "Name": "John",
  "Age": 21
},
{
  "Name": "Mary",
  "Age": 22
}]

What IAM permissions are needed for this to work?

I get this error message:

Amazon.SecretsManager.AmazonSecretsManagerException: User: [obfuscated] is not authorized to perform: secretsmanager:ListSecrets because no permissions boundary allows the secretsmanager:ListSecrets action

I have verified the user has Allow: secretsmanager:ListSecrets

Accessing secrets when MFA is enabled

Hello,

Version: 1.7.0
App: .Net 6 Web API

I am unable to use the extension when I have an MFA enabled.

I use the CredentialProfileStoreChain to get AWS credential the following wasy as shown in an example.

var chain = new Amazon.Runtime.CredentialManagement.CredentialProfileStoreChain();
_ = chain.TryGetProfile("nzp", out var prof);
var credentials = prof.GetAWSCredentials(prof.CredentialProfileStore);
builder.Configuration.AddSecretsManager(credentials: credentials, region:prof.Region, configurator: config =>
{
    config.KeyGenerator = (_, name) => name
        .Replace("__", ":");
});

I have a profile setup the following way.
.aws\config file

[default]

output = json

region = ap-southeast-2


[profile nzp]

source_profile = default

role_arn = arn:aws:iam::<account_id>:role/<role_name>

mfa_serial = arn:aws:iam::<account_id>:mfa/<user_id>

region = ap-southeast-2

I get following exception when secret manager is added.
Error calling AssumeRole for role arn:aws:iam::<account_id>:role/<role_name>

With inner exception as:
The MfaSerialNumber has been set but the MfaTokenCodeCallback hasn't. MfaTokenCodeCallback is required in order to determine the MfaTokenCode when MfaSerialNumber is set.

I tried following an example of getting an MFA. But unfortunately cannot use Console in Web API project.

Logging

Hi,

It would be great if we could add logging for certain situations (on request, , missing secrets, if JSON parsing fails etc).

Would you accept a PR?
If so would you want it with a simple Action callback in the options, or would you be happy with a way to bind an ILogging implementation somehow?

Thanks

Unable to get IAM security credentials from EC2 Instance Metadata Service

How can use AWS secret when I deploy into production. I'm using .net6 and used docker to deploy into azure but in production, I got the error "Unable to get IAM security credentials from EC2 Instance Metadata Service"
I'm adding credentials for AWS secret manager. It works when I change the environment variable to production but does not work when I deployed

I have tried 6 samples but nothing works for me

When I use Basic credentials I got

Amazon.Runtime.AmazonServiceException: Unable to get IAM security credentials from EC2 Instance Metadata Service.

I also add IAM full access to that user role

When I use StoredProfileAWSCredentials I got

App.config does not contain credentials information. Either add the AWSAccessKey and AWSSecretKey properties or the AWSProfileName property.

How to configure secrets manager when retrieving via IAM role?

One of our apps will consume an AWS secrets vault, and as it runs via Kubernetes, the associated service account will be assigned an IAM role. Question is, how do I configure my app with this library in order to successfully retrieve the secrets? With classic credentials, the code is straightforward:

Host.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((ctx, builder) =>
    {
        var c = builder.Build();
        var region = c.GetValue<string>("AWS_SECRETS_REGION");
        var name = c.GetValue<string>("AWS_SECRETS_NAME");
        var creds = new EnvironmentVariablesAWSCredentials(); //using environment variables for client ID and secret
        var configurator = new Action<SecretsManagerConfigurationProviderOptions>(opts =>
        {
            opts.SecretFilter = entry =>
            {
                return string.Equals(entry.Name, name, StringComparison.InvariantCulture);
            };
        });
        builder.AddSecretsManager(credentials: creds, region: RegionEndpoint.GetBySystemName(region), configurator: configurator);
    })

but I don't know how to do it when using an IAM role.

Sanitize secrets' names when using AddSecretsManager's AcceptedSecretArns option

Hi,

When using the option SecretFilter the secret is retrieved by its name
When using the option AcceptedSecretArns the secret is retrieved by its ARN

In SecretListEntry class there's no difference between values of Name and ARN properties although the summary of Name suggests it to be the friendly name of the secret.

Suggestion:
Make AcceptedSecretsArns return the expected friendly name in Name property so that could be used as path to the secret instead the current entire ARN.

Ignoring keys

Hi there,

thanks for creating this useful library. I would like to suggest providing a facility to skip key/value pairs in SecretManager somewhere if not easily with Options.KeyGenerator returning null to mean ignore the key?

The reason I want this feature is then I can have different sets of key/value pairs for each environment with the keys start with env name. Then I compare the key with envname and if not matching I will skip.

For instance:

dev-key1
dev-key2

uat-key1
uat-key2

then based on my own logic I will take key1 out of dev-key1 if the current environment in my code is development.

Debugging

Hi, I've done an implementation of your code, but I don't seem to be getting a value. Is there any way to see if it's a binding issue, or if Im not getting the value from secrets manager correctly?

builder.Configuration.AddSecretsManager(credentials, region: Amazon.RegionEndpoint.EUWest2,
        configurator: options =>
        {
            options.SecretFilter = entry => entry.Name.StartsWith("t"); // Tried using the entry name, ARN and removing this line.
            options.KeyGenerator = (e, s) =>
            {
                return "Database:Test";
            };
            //options.PollingInterval = TimeSpan.FromMinutes(2);
        });

        public static WebApplicationBuilder ConfigureDatabase(this WebApplicationBuilder builder)
        {
            IConfigurationSection databaseConfiguration = builder.Configuration.GetSection(DatabaseConfiguration.SectionName); // This value is null, Section name is `Database`
            builder.Services.Configure<DatabaseConfiguration>(databaseConfiguration);
            builder.Services.AddSingleton<IDatabaseClient, DatabaseClient>();
            return builder;
        }

image
I am using a profile as credentials, I am able to list the secrets using this profile through the AWS CLI and it has full AWS secrets manager access in IAM.

ECS: Unable to get IAM security credentials from EC2 Instance Metadata Service

I'm trying to use this in an ECS Task, and I'm getting the error "Unable to get IAM security credentials from EC2 Instance Metadata Service."

I have set up policies on my ECS Task role to provide access to the specific Secrets I am trying to access, as well as KMS and Session Manager, according to https://aws.amazon.com/premiumsupport/knowledge-center/ecs-data-security-container-task/ and a few other links.

Is there anything else that I'm missing that needs to be done to get this to work?

Lambda Caching Layer

Hello,

I'm using AWSSecretsManagerConfigurationExtensions with api running on Lambda. But i'm now facing the issue where the GetSecretValue limit of 10k read/seconds is reached.

I saw AWS provide a cache layer for secret and parameter that can be dirrectly link to a lambda function :
https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html

For example you can add this layer to your function with cloudformation like this :

      Layers:
        - arn:aws:lambda:eu-central-1:187925254637:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:4

Actually AWSSecretsManagerConfigurationExtensions is reading secret at each application start. in the case of .net api running on lambda, each invocations is reading secret.

Can we imagine an implementation of the cache layer of lambda ?

Regards

Provide my own IAmazonSecretsManager client

I do not have a default AWS profile setup as I have several different sets of credentials for different projects and AWS accounts. I would like to be able to provide my own IAmazonSecretsManager client and not have SecretsManagerConfigurationProviderOptions create one for me.

AWS provide an AWSSDK.Extensions.NETCore.Setup NuGet package with a GetAWSOptions extension method for IConfiguration that loads AWS options from the configuration. This has a CreateServiceClient method that takes a generic parameter T of type IAmazonService.

I would like to be able to use this with your AddSecretsManager extension method. It could be as simple as specifying the IAmazonSecretsManager that the SecretsManagerConfigurationSource will use, falling back to CreateClient if it is null.

For example SecretsManagerConfigurationSource could be changed like this:

public class SecretsManagerConfigurationSource : IConfigurationSource
{
    public SecretsManagerConfigurationSource(
        AWSCredentials credentials = null,
        SecretsManagerConfigurationProviderOptions options = null,
        IAmazonSecretsManager client = null)
    {
        Credentials = credentials;
        Options = options ?? new SecretsManagerConfigurationProviderOptions();
        Client = client;
    }

    public SecretsManagerConfigurationProviderOptions Options { get; }

    public IAmazonSecretsManager Client { get; }

    public AWSCredentials Credentials { get; }

    public RegionEndpoint Region { get; set; }

    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        var client = Client ?? CreateClient();
        
        return new SecretsManagerConfigurationProvider(client, Options);
    }

    private IAmazonSecretsManager CreateClient()
    {
...

The AddSecretsManager extension method could be changed like the below (even better idea: add overloads for the different nullable parameter combinations):

public static IConfigurationBuilder AddSecretsManager(
    this IConfigurationBuilder configurationBuilder,
    AWSCredentials credentials = null,
    RegionEndpoint region = null,
    Action<SecretsManagerConfigurationProviderOptions> configurator = null,
    IAmazonSecretsManager client = null)
{
    var options = new SecretsManagerConfigurationProviderOptions();

    configurator?.Invoke(options);

    var source = new SecretsManagerConfigurationSource(credentials, options, client);
...

Then I could build the configuration in my Startup class like this:

public Startup(IConfiguration configuration)
{
    var secretsManagerClient = configuration
        .GetAWSOptions("CustomAwsSettings")
        .CreateServiceClient<IAmazonSecretsManager>();

    Configuration = new ConfigurationBuilder()
        .AddConfiguration(configuration)
        .AddSecretsManager(client: secretsManagerClient)
        .Build();
}

Question on using SecretsManagerConfigurationProviderOptions.PollingInterval

I have set the PollingInterval with the intention that it refreshes the configuration values on a timed interval. I need this feature as our key rotation service refreshes the database password at regular intervals.

My assumption is that Entity Framework would pick up the new database credentials...but apparently, it does not. I had to go in and manually restart the service in order for it to pick up the new credentials.

Is there something else that I need to do in order for this process to run as intended?

I have the following code:

opts.PollingInterval = TimeSpan.FromMinutes(1)

Package dependency conflicts

Nuget refuses to add this package as a dependency because: NU1107: Version conflict detected for AWSSDK.Core. Install/reference AWSSDK.Core 3.5.1.54 directly to resolve this issue. Kralizek.Extensions.Configuration.AWSSecretsManager 1.4.1 -> AWSSDK.SecretsManager 3.3.0 -> AWSSDK.Core (>= 3.3.21.20 && < 3.4.0).

And when referencing the latest AWSSDK.Core package directly, Nuget would warn when restoring that Detected package version outside of dependency constraint: AWSSDK.SecretsManager 3.3.0 requires AWSSDK.Core (>= 3.3.21.20 && < 3.4.0) but version AWSSDK.Core 3.5.1.56 was resolved..

This makes it complicated to consume this package if one uses any other AWS package in their projects.

Use default region from credentials

Thanks for your work on this - replaced my own code with yours.

I would like to avoid specificying the region in code as this may change between development and production environments (for example).

As it cumbersome and anti-pattern to read a value from the configuration object during application configuration I would prefer if the region was using the default region (from the credentials profile).
I believe this should then work fine on development machines, EC2 instances and Lambdas.

I'm happy to create a PR for this if you think it makes sense as well.

Async loading causes blocking during test execution

πŸ‘‹ I'm back!

I've been updating our integration tests (after a couple months of neglect) and encountered an issue with using this configuration provider. A decent description of the issue (and how to reproduce) is available at aspnet/Configuration#783 . In short, when using xunit test collections and having multiple, it tends to create a deadlock while attempting to load configuration settings.

The only way I've found to make this work is doing a bad thing and updating the calls to IAmazonSecretsManager to force synchronous execution by using GetAwaiter().GetResult(). What would be your thoughts on switching to this?

Thanks!

Release non-prerelease version

Hi, thanks for this package - does exactly what I needed, and meant I didn't need to write the same thing!

I just have one request - please could you do a 1.0.0 release (i.e. not alpha)? Currently, if you try and reference this package from another (non pre-release) library you'll get warnings when packing NuGet packages:

C:\Program Files\dotnet\sdk\2.1.302\Sdks\NuGet.Build.Tasks.Pack\buildCrossTargeting\NuGet.Build.Tasks.Pack.targets(198,5): warning NU5104: A stable release of a package should not have a prerelease dependency. Either modify the version spec of dependency "Kralizek.Extensions.Configuration.AWSSecretsManager [1.0.0-alpha-2, )" or update the version field in the nuspec.

Thanks! πŸ™‚

Update for .NET Core 6?

Hello - can the sample code get updated for .NET Core 6, since the Startup and Program.cs files are not one in the same, and this all looks different now. Thanks!

AWS X-Ray?

Is there any way to include AWSSecretsManagerConfigurationExtensions requests in an AWS X-Ray trace?

LambdaException

All has been working fine using this lib inside AWS Lambda but today we get this stack trace when our lambda/.net app starts. Any ideas?

at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean wrapExceptions, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean wrapExceptions, Boolean skipCheckThis, Boolean fillCache)
The operation was canceled.: OperationCanceledException
at System.Net.Http.HttpClient.HandleFinishSendAsyncError(Exception e, CancellationTokenSource cts)
at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
at Amazon.Runtime.HttpWebRequestMessage.GetResponseAsync(CancellationToken cancellationToken)
at Amazon.Runtime.Internal.HttpHandler`1.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.Unmarshaller.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.CredentialsRetriever.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.ErrorCallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
at Amazon.Runtime.Internal.MetricsHandler.InvokeAsync[T](IExecutionContext executionContext)
at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.FetchAllSecretsAsync()
at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.LoadAsync()
at Kralizek.Extensions.Configuration.Internal.SecretsManagerConfigurationProvider.Load()
at Microsoft.Extensions.Configuration.ConfigurationRoot..ctor(IList`1 providers)
at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
at Microsoft.AspNetCore.Hosting.WebHostBuilder.BuildCommonServices(AggregateException& hostingStartupErrors)
at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
at Amazon.Lambda.AspNetCoreServer.AbstractAspNetCoreFunction`2.Start()

Change Request: Use 'AWSSDK.SecretsManager.Caching' internally

From the looks of it, this library has custom in-memory caching of the secrets it reads from AWS Secrets Manager. At the same time, there exists a supported library from AWS that has native inmemory caching for the calls.

I believe it would be beneficial for this provider to rely on that existing implementation instead of creating its own version.

Document that `AcceptedSecretArns` supports friendly names & partial ARN's

The SecretsManagerConfigurationProviderOptions.AcceptedSecretArns property's name suggests that it only supports full ARN's. However, the value's specified here are used as the SecretId in a GetSecretValueRequest, which means they can be:

  • Full ARN (including random 6 characters at end)
  • Partial ARN (excluding random 6 characters at end)
  • Friendly Name

It would be good if either:

  • The property name could be updated to remove the word ARN, since it's not specific to ARNs (I doubt this is a feasible option since it's a breaking change).
  • Add XML documentation on this property (and ideally, on all properties, methods etc).
  • Add more information in the relevant README section to let users know what can be used here

Update dependencies

Current version of AWSSDK.SecretsManager (3.3.0) requires AWSSDK.Core versions between 3.3.21.20 & 3.4.0. We had to downgrade a lot of AWSSDK.* packages to prevent incompatibility. Would it be possible to update the dependencies to the latest versions available. I tried and successfully an all the tests on them.
Will also be more than happy to help with maintaining the repo.

Specify --version-stage AWSCURRENT in GetSecretValue

Background

There are times when it's required to specify the version stage when performing secretsmanager:GetSecretValue.

In my case, when accessing a secret in a different AWS account, that has been shared to a credential I've grabbed from the CredentialProfileStoreChain, I am only able to access the secret when specifying --version-stage AWSCURRENT.

aws secretsmanager get-secret-value \
  --region us-west-2 \
  --secret-id arn:aws:secretsmanager:us-west-2:{redacted}:secret:{redacted} \
  --profile sharedsecretprofile \
  --version-stage AWSCURRENT

In the previous code block, when specifying --version-stage AWSCURRENT, I am able to retrieve the secret. Without it, I get the error

An error occurred (AccessDeniedException) when calling the GetSecretValue operation: User: arn:aws:sts::{redacted}:assumed-role/{redacted}/{redacted} is not authorized to perform: secretsmanager:GetSecretValue on resource: arn:aws:secretsmanager:us-west-2:{redacted}:secret:{redacted} because no resource-based policy allows the secretsmanager:GetSecretValue action

The AWS documentation says it is set to AWSCURRENT by default, but that is clearly not true based on the previous example.

Proposal

Add VersionStage = "AWSCURRENT" to the GetSecretValueRequest.

I believe this change won't have any affect on any users as not specifying the stage should have the same affect as specifying AWSCURRENT as the stage.

Changes

Update SecretsManagerConfigurationProvider#L218 from

var secretValue = await Client.GetSecretValueAsync(new GetSecretValueRequest { SecretId = secret.ARN }, cancellationToken).ConfigureAwait(false);

to

var secretValue = await Client.GetSecretValueAsync(new GetSecretValueRequest { SecretId = secret.ARN, VersionStage = "AWSCURRENT" }, cancellationToken).ConfigureAwait(false);

I'd be happy to make this change if it's decided the change is okay.

Issues against Localstack

Hi,

I am quite new to AWS stuff and I am using your lib - btw, thanks for it - based on examples provided. I am using it from ASP.NET Core 2.2 app against AWS Localstack running in Docker. I found a few issues:

  1. I had to set:

opts.ConfigureSecretsManagerConfig = c => { c.AuthenticationRegion = <my_non_default_region_system_name> };

for it to work (in addition to region parameter for AddSecretsManager extension method). Otherwise, it was not failing, but no response (empty list) of secrets had been silently returned (200 response code from Secrets Manager in LS) in FetchAllSecretsAsync method.

  1. Deleted secrets are not filtered by default. (I filtered them out in my SecretFilter implementation). Might be nice option (true/filtered out by default) to filter them automatically (SecretsManagerConfigurationProvider.LoadAsync/FetchAllSecretsAsync method).

  2. For some reason, ARNs (their UUID suffix) seem to be changed by LS Secrets Manager with every request... Therefore, GetSecretValueAsync (inside SecretsManagerConfigurationProvider.LoadAsync loop) failed not being able to find requested resource/secret value (based on ARN passed from FetchAllSecretsAsync result). Changing the search criterion to secretValueRequest.SecretId = secret.Name; helped me (I am using prefixed keys like suggested here).

In case some of my findings are relevant, you can consider them for reflecting in the next version of the package.

Thanks.

Continuous JsonReaderExceptions thrown every couple seconds

I just upgraded to v1.6.0 today and noticed that I'm getting these exceptions every couple of seconds while the app is idle:
Exception thrown: 'Newtonsoft.Json.JsonReaderException' in Newtonsoft.Json.dll ("Unexpected character encountered while parsing number: G. Path '', line 1, position 7.")

Here is the call stack:
image

image

Retry logic in credentials polling

Is it worth considering retry logic in credential polling functionality if multiple microservices try to access the same secret value and due to rate limiting functionality code fails to retrieve the updated credentials?

Secret filtering on fetch unit test is unclear

This passing test in SecretsManagerConfigurationProviderTests appears to be attempting a test on filtering:

[Test, CustomAutoData]
public void Secrets_can_be_filtered_out_via_options_on_fetching([...])
{
    options.ListSecretsFilters = new List<Filter> { new Filter { Key = FilterNameStringType.Name, Values = new List<string> { testEntry.Name } } };

    Mock.Get(secretsManager).Setup(p => p.ListSecretsAsync(It.Is<ListSecretsRequest>(request => request.Filters == options.ListSecretsFilters), It.IsAny<CancellationToken>())).ReturnsAsync(listSecretsResponse);

    sut.Load();

    Mock.Get(secretsManager).Verify(p => p.ListSecretsAsync(It.Is<ListSecretsRequest>(request => request.Filters == options.ListSecretsFilters), It.IsAny<CancellationToken>()));

    Assert.That(sut.Get(testEntry.Name), Is.Null);
}

However removing the first line where the filters are set still yields a passing test:
// options.ListSecretsFilters = new List<Filter> { new Filter { Key = FilterNameStringType.Name, Values = new List<string> { testEntry.Name } } };

As does changing the final assert statement to a random key:
Assert.That(sut.Get("hello"), IsNull);

It appears that the only behavior being tested is that ListSecretsAsync() is being called when the request filters match the options list filters.

I honestly can't tell whether setting options.ListSecretsFilters is intended to omit results in the filter list or include them.
The documentation seems to say include, but asserting that the filtered value will be null appears to indicate the opposite.

Either way, the test doesn't appear check the intended behavior because sut.Get(testEntry.Name) always returns null.

Maybe I'm missing something, but it seems like a more useful test would be a check to see whether a filtered request does return the desired values.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.