Securing .NET App Secrets with AWS Secrets Manager
Kurt Feeley
Posted on June 2, 2022
AWS Systems Manager Parameter Store is a great all around addition to your configuration and secrets management story. Parameter Store can be a cost effective solution as there isn't a charge for standard parameters. Parameter Store supports the storage of common configuration data like a URL (String Type) and data that's a bit more complex like a list of OAuth2 scopes (StringList Type) but, AWS Systems Manager Parameter Store also supports more sensitive configuration data like secrets, passwords and tokens (SecureString Type).
So, why use AWS Secrets Manager? AWS Secrets Manager features automated secret rotation and direct integration with services like RDS, Redshift, and DocumentDB. So, if you need to automatically rotate secrets or need integration with data storage technologies like RDS, Redshift, and DocumentDB, AWS Secrets Manager may be the right choice for you.
In this post we’ll focus on AWS Secrets Manager, but if AWS Systems Manager Parameter Store sounds more like your thing, check out this post on Using AWS Systems Manager Parameter Store as a .NET Configuration Provider.
Photo by saeed karimi on Unsplash
The Solution
In this article, we’ll take a look at using AWS Secrets Manager to store and retrieve confidential data. We’ll create a .NET API as the reference application and we'll use the AWS CLI and a .NET library developed by AWS that makes this process simple.
Prerequisites
To complete this solution, you will need the .NET CLI which is included in the .NET 6 SDK. In addition, you will need to download the AWS CLI and configure your environment for the AWS CLI. You will also need to create an AWS IAM user with programmatic access with the appropriate permissions to create and read secrets in AWS Secrets Manager.
Warning: some AWS services may have fees associated with them.
The Dev Environment
This tutorial was developed using Ubuntu 20.04, AWS CLI v2, .NET 6 SDK and Visual Studio Code 1.66.2. Some commands/constructs may very across systems.
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”: “txRxQ6#[Muq_%oVVg,0vLrDJ;7{GG^Gy”
}
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 'txRxQ6#[Muq_%oVVg,0vLrDJ;7{GG^Gy'
On completion, you will get a response with the ARN, Name and VersionId.
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)
);
These lines will setup the dependency injection so that when a class requires an IAmazonSecretsManager based class, an AmazonSecretsManagerClient will be supplied.
The Program.cs file should now look like:
using Amazon;
using Amazon.SecretsManager;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IAmazonSecretsManager>(a =>
new AmazonSecretsManagerClient(RegionEndpoint.USEast1)
);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
To demonstrate the use of Secrets Manager, let’s create a Controller named SecretController.cs. In the SecretController class, let's create a GetSecret method with the following logic.
This exercise is obviously for demonstration purposes only and not based on a true use case.
[HttpGet]
public async Task<IActionResult> GetSecret()
{
GetSecretValueRequest request = new GetSecretValueRequest
{
SecretId = "test-secret",
VersionStage = "AWSCURRENT"
};
GetSecretValueResponse response = await _secretsManager.GetSecretValueAsync(request);
return Ok(new { Secret = response.SecretString });
}
Here, we instantiate the GetSecretValueRequest object assigning the SecretId for the secret that we are trying to fetch and also ask for the latest version of the secret by setting the VersionStage to AWSCURRENT. The last step is to send the request and parse the response.
Let’s 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
{
SecretId = "test-secret",
VersionStage = "AWSCURRENT"
};
GetSecretValueResponse response = await _secretsManager.GetSecretValueAsync(request);
return Ok(new { Secret = response.SecretString });
}
}
Testing the Application
First, let’s get the "Api" application up and running with the following .NET CLI command:
$ dotnet run ––project Api/
*Note, here we have configured the app to run on port 5000. Your app port may very.
In your favorite browser, let’s navigate to http://localhost:5000/secret. You should see something like the following:
{
"secret": "txRxQ6#[Muq_%oVVg,0vLrDJ;7{GG^Gy"
}
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”: “0)r}|iRvK2H,%<R9tAJNDu<M@gw*OUD-”
}
Copy the generated password and then let’s update the secret like so:
$ aws secretsmanager put-secret-value \
––secret-id test-secret \
––secret-string ‘0)r}|iRvK2H,%<R9tAJNDu<M@gw*OUD-’
On completion, you will see a response with values for ARN, Name, VersionId, and VersionStages.
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 response should look something like this:
{
"secret": "0)r}|iRvK2H,%<R9tAJNDu<M@gw*OUD-"
}
With our tests complete, let’s delete that secret.
$ aws secretsmanager delete-secret ––secret-id test-secret
Summary
That’s it! We have concluded this post where we went over reading AWS Secrets Manager secrets from within a .NET application as well as creating, updating and deleting secrets in AWS Secrets Manager via the AWS CLI.
Posted on June 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.