Securing .NET App Secrets with AWS Secrets Manager

Similar to AWS Systems Manager Parameter Store, AWS Secrets Manager allows for storing, managing, and reclaiming OAuth tokens, database credentials, API keys, and other secrets. However, there are big differences between the two AWS services.

AWS Secrets Manager was created for storing confidential data like passwords and secrets so encryption of the stored data is enabled by default. Where as Parameter Store was created to store confidential data as well as general configuration data, like URIs, licensing keys, UNC network paths, and the like. So, understandably, encryption is optional with AWS Systems Manager Parameter Store. Additionally, AWS Secrets Manager features automated key rotation and direct integration with services like RDS, Redshift, and DocumentDB.

Fortunately, there are. NET libraries that make integrations with AWS Secrets Manager and AWS Systems Manager Parameter Store very straightforward. In this post we’ll focus on AWS Secrets Manager, but checkout our other tutorial on AWS Systems Manager Parameter Store.

The Solution

In this article, we’ll take a look at using AWS Secrets Manager to store and retrieve confidential data. We’ll use a .NET library developed by AWS that makes this process simple.

Remember, for any example solution from AWS with .NET, we focus on the code that exemplifies the problem we are trying to solve. We don’t include logging, input validation, exception handling, etc., and we embed the configuration data within classes instead of using environment variables, configuration files, key/value stores and the like. These items should not be skipped for proper solutions.

Prerequisites

To complete this solution, you will need the .NET CLI which is included in the .NET SDK. In addition, you will need to download the AWS CLI and configure your environment. You will also need to create an AWS IAM user with programmatic access with the appropriate permissions to create secrets in AWS Secrets Manager.

Warning: some AWS services may have fees associated with them.

Store Secrets with the AWS CLI

Using the AWS CLI, we’ll first create a random password.

$ aws secretsmanager get-random-password

The response will look something like the following:

{
“RandomPassword”: “B2?P6vM<]Eo1{h\G8|McZni@LBrh|S)5”
}

Let’s now store that password in AWS Secrets Manager. Take note of the name of the secret.

$ aws secretsmanager create-secret ––name test-secret ––secret-string ‘B2?P6vM<]Eo1{h\G8|McZni@LBrh|S)5’

Create the .NET Test Application

With the secret created, let’s create a test application that integrates with AWS Secrets Manager which will allow us to retrieve the stored secret. For this example, we will use a .NET API.

$ dotnet new webapi ––name Api

Now that we have a reference application, let’s pull in the Nuget that contains the AWS Secrets Manager Caching library with the following command.

$ dotnet add Api/ package AWSSDK.SecretsManager.Caching

Let’s go into the Program.cs file within the new Api application and add a few lines directly below the builder variable declaration:

builder.Services.AddScoped<IAmazonSecretsManager>(a =>
new AmazonSecretsManagerClient(RegionEndpoint.USEast1)
);
view raw Startup.cs hosted with ❤ by GitHub

These lines will setup the dependency injection so that when a class requires an IAmazonSecretsManager based class, an AmazonSecretsManagerClient will be supplied.

To demonstrate the use of Secrets Manager, let’s create a Controller named SecretController.cs.

In that class, let’s inject an IAmazonSecretsManager based object via the constructor and set a corresponding field to that passed IAmazonSecretsManager object.

public SecretController(IAmazonSecretsManager secretsManager)
{
_secretsManager = secretsManager;
}

Next, we need to create a method, GetSecret.

[HttpGet]
public IActionResult GetSecret()
{
}

Within that method, let’s create a request to retrieve the value that we entered via the CLI. In doing so, we specify the SecretId, which was the name parameter from the previous CLI command. We also specify that we want the most recent version by requesting a version stage (VersionStage) of AWSCURRENT.

GetSecretValueRequest request = new GetSecretValueRequest();
request.SecretId = "test-secret";
request.VersionStage = "AWSCURRENT";

Checkout the GetSecret method in its entirety.

[HttpGet]
public async Task<IActionResult> GetSecret()
{
GetSecretValueRequest request = new GetSecretValueRequest();
request.SecretId = "test-secret";
request.VersionStage = "AWSCURRENT";
GetSecretValueResponse response = await _secretsManager.GetSecretValueAsync(request);
return Ok(new { Secret = response.SecretString });
}

And, let’s also take a look at the complete SecretController class.

using Amazon.SecretsManager;
using Amazon.SecretsManager.Model;
using Microsoft.AspNetCore.Mvc;
namespace Api.Controllers;
[ApiController]
[Route("[controller]")]
public class SecretController : ControllerBase
{
private readonly IAmazonSecretsManager _secretsManager;
public SecretController(IAmazonSecretsManager secretsManager)
{
_secretsManager = secretsManager;
}
[HttpGet]
public async Task<IActionResult> GetSecret()
{
GetSecretValueRequest request = new GetSecretValueRequest();
request.SecretId = "test-secret";
request.VersionStage = "AWSCURRENT";
GetSecretValueResponse response = await _secretsManager.GetSecretValueAsync(request);
return Ok(new { Secret = response.SecretString });
}
}

Now, let’s give everything a test.

First, let’s get the Api application up and running with the following dotnet command:

$ dotnet run ––project Api/

In your favorite browser, let’s navigate to http://localhost:5000/secret. You should see something like the following:

{
   "secret": "B2?P6vM<]Eo1{h\G8|McZni@LBrh|S)5"
}

Keep the browser open for a test after we practice a couple more commands.

Let’s go back to the CLI and create a new password to store.

$ aws secretsmanager get-random-password

Again, you should see something like the following:

{
“RandomPassword”: “f1\~FKViNOwg%HMEqx?f3PW’4bY@-pXR”
}

Copy the generated password and then let’s update the secret like so:

$ aws secretsmanager put-secret-value ––secret-id test-secret ––secret-string ‘f1\~FKViNOwg%HMEqx?f3PW’4bY@-pXR’

Let’s return to the browser and give it a refresh. If you closed the browser, just reopen the browser and browse to: http://localhost:5000/secret. You should now see the value you just entered for the secret and the value returned should look something like this:

{
   "secret": "f1\\~FKViNOwg%HMEqx?f3PW'4bY@-pXR"
}

With our tests complete, let’s delete that secret.

$ aws secretsmanager delete-secret ––secret-id test-secret

Challenges

Now that you have a working solution, let’s take things a little further…

  • Store the SecretId in configuration.
  • Inject an IConfiguration based object into the secret controller.
  • Pull the SecretId from configuration.
  • Setup rotation for a secret in the AWS console.
  • Create multiple versions of a secret in AWS Secrets Manager.

That’s it! We have concluded this article where you have learned how to create, read, update and delete secrets in AWS Secrets Manager via the AWS CLI as well as read AWS Secrets Manager secrets from within a .NET application.

Get all the code on GitHub.

Want to know more about the tech in this article? Checkout these resources:

.NET CLI

AWS CLI 

Configuring the AWS CLI

ASP.NET Web APIs

AWS Secrets Manager

Using AWS Secrets Manager via the AWS CLI