AWS Secrets

Secrets are any authentication details like a password that allow a user potentially using authorization code flow or a service potentially using client credentials flow to verify their identity.

AWS has a few ways to manage these secrets and often functionality overlaps, this is how I have found these to be useful.

Pre-cursor

The IAM role will need these policy statements

  • Action: kms:Decrypt
  • Action: ssm:GetParameters
  • Action: secretsmanager:GetSecretValue (if using secretsmanager programatically which is not the flows described below)

AWS Systems Manager (SSM) - Parameter Store

Use Case: Simple encrypted secret storage for your application to access

Note that KMS does support rotation-period if you wish.

AWS Systems Manager (SSM) with Parameter Store (Secure String) is a great way to store secrets which your application can read at runtime, then store as environment variables. With .Net these can override appsettings. To get started you follow these high level steps

  1. Create a Key Management Service (KMS) key, for the purpose of simple data encryption at rest, you can use a symmetric encryption KMS key. I normally use the AWS console, so their website to create these keys but you can also use the CLI with command aws kms create-key

You will need the KMS-KEY-ARN, it will look something like arn:aws:kms:{REGION}:{ACCOUNT-NUMBER}:key/mrk-00000000000000000000000000000000

  1. Create parameter, the simplest way is with the CLI because its hidden in the console and I always have to click around to find it
1
2
3
4
5
6
7
aws ssm put-parameter \
--name '{NAME}' \
--description '{DESCRIPTION}' \
--value '{PLAIN-TEXT-SECRET-VALUE}' \
--type SecureString \
--key-id '{KMS-KEY-ARN}' \
--tags '[{"Key":"created-with","Value":"aws-cli"}]'
  1. Using the KMS key access the secret from parameter store, at a high level this could look like

Create a GetSecureParameterAsync in an abstracted service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using Amazon;
using Amazon.Runtime;
using Amazon.SSM;
using Amazon.SSM.Model;

public static async Task<string> GetSecureParameterAsync(string parameterName, string regionEndpoint)
{
// Specify your AWS credentials. If running on an EC2 instance with an IAM role
// or if your AWS CLI is configured, you can often omit this.
// Replace with your actual access key and secret key if needed.
// var credentials = new BasicAWSCredentials("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY");
// var config = new AmazonSSMConfig { RegionEndpoint = RegionEndpoint.GetBySystemName(regionEndpoint) };
// using (var ssmClient = new AmazonSSMClient(credentials, config))

// If you have default AWS credentials configured (e.g., IAM role, AWS CLI), you can use:
using (var ssmClient = new AmazonSSMClient(RegionEndpoint.GetBySystemName(regionEndpoint)))
{
var request = new GetParameterRequest
{
Name = parameterName,
WithDecryption = true // This is crucial for secure parameters
};

try
{
var response = await ssmClient.GetParameterAsync(request);
return response.Parameter.Value;
}
catch (AmazonSSMException e)
{
Console.WriteLine($"Error retrieving parameter '{parameterName}': {e.Message}");
return null;
}
}
}

Call the method GetSecureParameterAsync and use the secret in your application.

1
2
3
4
5
6
7
8
9
10
11
12
13
string parameterName = "/path/to/your/secret";
string awsRegion = "ap-southeast-2";

string secretValue = await GetSecureParameterAsync(parameterName, awsRegion);

if (!string.IsNullOrEmpty(secretValue))
{
Console.WriteLine($"Successfully retrieved secret '{parameterName}': {secretValue}");
}
else
{
Console.WriteLine($"Failed to retrieve secret '{parameterName}'.");
}

Secrets Manager

Use Case: Simple encrypted secret storage for secrets you want to rotate and generally access manually by humans

Note, you can also programatically access Secrets Manager if you wish.

  1. Create the KMS, same as details above.

  2. Create the secret, the simplest way is with the CLI

1
2
3
4
5
6
aws secretsmanager create-secret \
--name '{NAME}' \
--description '{DESCRIPTION}' \
--key-id '{KMS-KEY-ARN}' \
--secret-string '{PLAIN-TEXT-SECRET-VALUE}' \
--tags '[{"Key":"created-with","Value":"aws-cli"}]'
  1. Access the secret manually in the console

If you want to instead use this for secrets in your code, the method to get the secret could look like

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public static async Task<string> GetSecretValueAsync(string secretName, string regionEndpoint)
{
// Specify your AWS credentials if needed (less recommended for production).
// var credentials = new BasicAWSCredentials("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY");
// var config = new AmazonSecretsManagerConfig { RegionEndpoint = RegionEndpoint.GetBySystemName(regionEndpoint) };
// using (var client = new AmazonSecretsManagerClient(credentials, config))

// Use default AWS credentials (IAM role, AWS CLI, etc.) - recommended for production.
using (var client = new AmazonSecretsManagerClient(RegionEndpoint.GetBySystemName(regionEndpoint)))
{
var request = new GetSecretValueRequest
{
SecretId = secretName
};

try
{
var response = await client.GetSecretValueAsync(request);

// Secrets can be a string or binary data.
if (!string.IsNullOrEmpty(response.SecretString))
{
return response.SecretString;
}
else if (response.SecretBinary != null)
{
// Handle binary secret (e.g., decode from Base64)
using (var reader = new System.IO.StreamReader(response.SecretBinary))
{
return await reader.ReadToEndAsync(); // Or process as needed
}
}
else
{
Console.WriteLine($"Secret '{secretName}' has no String or Binary value.");
return null;
}
}
catch (AmazonSecretsManagerException e)
{
Console.WriteLine($"Error retrieving secret '{secretName}': {e.Message}");
return null;
}
}
}