Secure your application with Aws Secrets Manager 🔒
Tags: aws, cloud, dotnet, softwaredevelopment
Published: 2024-01-19 16:28:25

One of the main tasks of a software developer is to make code as secure as possible, and to avoid using sensitive data such as database connection strings, passwords, or any other type of data directly in code.
AWS Secrets Manager makes it very easy to improve the security of your application, in this post I will show you an example of how you can use this service in a dotnet application to avoid hard-coded data.

**What is Aws Secrets Manager ?**
Secrets Manager helps us improve the security of applications by eliminating the need for hard-coded credentials in the application source code. Hard-coded credentials are replaced with a runtime call to Secret Manager (or other mechanisms) to dynamically retrieve credentials when you need them.
With this service you can manage secrets such as database credentials, on-premises resource credentials, SaaS application credentials, third-party API keys, and Secure Shell (SSH) keys.
Aws Secrets Manager also allows for automatic secret rotation, which means that it is possible to schedule the update of the credentials contained in the secrets without having to touch the code (topic not covered in this post, but maybe it will be a topic for a future post).


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/daeqepsur3sdhszb1acg.png)

**Create a Secret on Aws Secrets Manager**

The first step is to create a secret in your Aws account.
To do this, go to the Aws Secrets Manager section in your console and click Save New Secret.
There are different types of stores you can choose from, but what is right for us is Other type of secret, because with this type of secret you can store many key/value properties.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1kd6y7f2m970a754mx3.png)

Now you can enter, for example, the connection string or any other value you want to store in this secret.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xe4308hzvmk0b0gg5c0i.png)

Make sure to apply an AWS KMS (Key Management Service) key. You can apply a predefined key or specify your own key, if one exists.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/80039qklkflm1a68p510.png)

Click on the next step and enter the secret name and description, you can also add a tag if you wish, before proceeding to the last step.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sxk65ye0467msdiqpqhi.png)

In the last step you can set up a secret rotation, but that's not a topic of this post. Then leave this option unchecked.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vlq4yyfj0l5ayvxiccj7.png)

Check that you have filled in all the fields correctly and then click Store.
Ok, now you have a new secret configured on Aws. 

If you prefer to use Terraform to create this resources in Aws, you can follow this example.
```
resource "aws_secretsmanager_secret" "my_secret" {
  name                    = "secrets-${lower(var.name)}-${lower(var.environment)}"
  recovery_window_in_days = 7
  
  lifecycle {
    prevent_destroy = true
  }
  
  tags = {
    Project     = lower(var.name)
    Name        = "secrets-${lower(var.name)}-rds-${lower(var.environment)}"
    Environment = upper(var.environment)
  }
}

resource "aws_secretsmanager_secret_version" "my_secrets_version" {
  secret_id = aws_secretsmanager_secret.my_secret.id

  lifecycle {
    prevent_destroy = true
  }

  secret_string = <<EOF
  {
    "ConnectionString": "Server=${aws_rds_cluster.rds_cluster.endpoint};Database=${lower(var.name)};Uid=${aws_rds_cluster.rds_cluster.master_username};Pwd=${aws_rds_cluster.rds_cluster.master_password};",
  }
  EOF
}`
```


**Use a Secret in a dotnet application**

The second step is to retrieve these values from Aws Secrets Manager. A dotnet application is required, so you can start by creating a new ASP.NET Core web application.

> 
dotnet new webapi --name AwsSecretManager

> 

Add the necessary dependencies:

> 
Install-Package AWSSDK.Extensions.NETCore.Setup
Install-Package AWSSDK.SecretsManager

> 

The first thing you need to do when you open the project is edit the appsettings.json file to add the aws section with the Secrets Manager configuration properties.

```
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "Aws": {
    "Region": "eu-west-1", // region where the secrets are present
    "SecretManagerName": "MySecret" // The name of the secret
  }
}

```

Now you need to add the following classes to your project, to create a custom ConfigurationProvider to handle the values ​​retrieved from Secrets Manager.

```
 public class SecretManagerProvider : ConfigurationProvider
 {
     private readonly string _region;
     private readonly string _secretName;

     public SecretManagerProvider(string region, string secretName)
     {
         _region = region;
         _secretName = secretName;
     }

     public async override void Load()
     {
         var secret = await GetSecret();
         Data = JsonSerializer.Deserialize<Dictionary<string, string>>(secret);
     }

     public async Task<string> GetSecret()
     {
         IAmazonSecretsManager client = new AmazonSecretsManagerClient(RegionEndpoint.GetBySystemName(_region));

         var request = new GetSecretValueRequest()
         {
             SecretId = _secretName,
             VersionStage = "AWSCURRENT",
         };

         var response = await client.GetSecretValueAsync(request);

         return response.SecretString;
     }
 }
```

```
public class SecretManagerSource : IConfigurationSource
{
    private readonly string _region;
    private readonly string _secretName;

    public SecretManagerSource(string region, string secretName)
    {
        _region = region;
        _secretName = secretName;
    }

    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        return new SecretManagerProvider(_region, _secretName);
    }
}
```

```
    public static class Secret
    {
        public static void AddAmazonSecretsManager(
          this IConfigurationBuilder configurationBuilder,
          string region,
          string secretName)
        {
            var configurationSource = new SecretManagerSource(region, secretName);
            configurationBuilder.Add(configurationSource);
        }
    }
```

```
public class SecretCredentials
{
    public string ConnectionString { get; set; }
    public string EncryptionKey{ get; set; }
    public string BucketBasePath{ get; set; }
}
```


>
The SecretManagerProvider class is a custom configuration provider that retrieves values from the Secrets Manager service. The SecretManagerSource class implements IConfigurationSource, which essentially exposes a key/value store containing configuration values.
The GetSecretValueRequest object uses a property called VersionStage, which specifies a type of version of the secret you want to use. There are 3 types of versions: AWSCURRENT, AWSPREVIOUS, AWSPENDING (during rotation). A secret always has a version labelled AWSCURRENT, and Secrets Manager returns this version by default when you retrieve the secret value.
> 



**How can you retrieve these values from within code?**
We need to use the IOptions<T> interface, which is used to access and manage configuration options for an application. The generic T parameter specifies the type of options to be managed.
To use IOptions<T>, you must first register the options with the dependency injection container, then you can add the following code.

```
builder.Configuration.AddAmazonSecretsManager(builder.Configuration["Aws:Region"], builder.Configuration["Aws:SecretManagerName"]);
builder.Services.Configure<SecretCredentials>(builder.Configuration);
```

If you want to access secret credentials for a particular class, you need to inject the IOptions<SecretCredentials> value into a costructor class. For Example:

```
public readonly IOptions<SecretCredentials> _secret;

public MySpecificClass(IOptions<SecretCredentials> secrets)
{
   _secrets = secrets;
}
```

The complete source code example is available on [GitHub](https://github.com/dbanieles/dotnet-aws-secrets-manager).


**Practical uses**
In your on-premises environment, it is possible to use Aws Secrets Manager with the AWS profile configured on the PC, but the user using the service must have the correct policy to manage that service. If you are running this application in the AWS cloud, for example on the EC2 or ECS service, make sure that these services have the correct policy associated with the role used to retrieve values from Aws Secrets Manager.

An example of Aws policy:

```
{
    "Version":"2012-10-17",
    "Statement": [
      {
          "Effect": "Allow",
          "Action": [
              "secretsmanager:GetSecretValue",
              "secretsmanager:GetRandomPassword",
              "secretsmanager:DescribeSecret",
              "secretsmanager:PutSecretValue",
              "secretsmanager:UpdateSecretVersionStage"
          ],
          "Resource": "*"
      }
    ]
}
```

**Links**

- [Aws Docs](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html)
- [Terraform Docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret)
- [Dotnet configuration provider](https://learn.microsoft.com/en-us/dotnet/core/extensions/custom-configuration-provider)