AWS Secrets Manager helps us to manage, retrieve, and rotate database credentials, application credentials, OAuth tokens, API keys, and other secrets throughout their lifecycles. Many AWS services store and use secrets in Secrets Manager.

Secrets Manager also helps us to improve our security posture, because we no longer need hard-coded credentials in application source code. Storing the credentials in Secrets Manager helps avoid possible compromise by anyone who can inspect our application or the components. We replace hard-coded credentials with a runtime call to the Secrets Manager service to retrieve credentials dynamically when our application need them.

With Secrets Manager, we can configure an automatic rotation schedule for our secrets. This enables us to replace long-term secrets with short-term ones, significantly reducing the risk of compromise. Since the credentials are no longer stored with the application, rotating credentials no longer requires updating the application codes and deploying changes to application clients.

Envelope Encryption

Secrets Manager uses envelope encryption with AWS KMS keys and data keys to protect each secret value. Whenever the secret value in a secret changes, Secrets Manager requests a new data key from AWS KMS to protect it. The data key is encrypted under a KMS key and stored in the metadata of the secret. To decrypt the secret, Secrets Manager first decrypts the encrypted data key using the KMS key in AWS KMS.

Secrets Manager does not use the KMS key to encrypt the secret value directly. Instead, it uses the KMS key to generate and encrypt a 256-bit Advanced Encryption Standard (AES) symmetric data key, and uses the data key to encrypt the secret value. Secrets Manager uses the plaintext data key to encrypt the secret value outside of AWS KMS, and then removes it from memory. It stores the encrypted copy of the data key in the metadata of the secret.

Example Code with Cache

               +---------------------+
               | Application Server
               |  (EC2, Lambda, etc.)
               +---------+-----------+
                         |
                         |
                         v
               +---------+-----------+
               |      Cache Layer
               |(AWS ElastiCache,
               | In-memory Cache)
               +---------+-----------+
                         |
                         |
                         v
               +---------+-----------+
               |   AWS Secrets
               |      Manager
               +---------------------+

Ruby with Cache

require 'aws-sdk-secretsmanager'
require 'json'

# Simple in-memory cache
$cache = {}

# TTL (time-to-live) in seconds for cache entries
CACHE_TTL = 3600

def cache_get(key)
  entry = $cache[key]
  if entry && (Time.now.to_i - entry[:timestamp] < CACHE_TTL)
    entry[:value]
  else
    nil
  end
end

def cache_set(key, value)
  $cache[key] = { value: value, timestamp: Time.now.to_i }
end

def get_secret(secrets_client, secret_name)
  # Check if secret is in cache
  cached_secret = cache_get(secret_name)
  return JSON.parse(cached_secret) if cached_secret

  # If not in cache, retrieve from Secrets Manager
  response = secrets_client.get_secret_value({
    secret_id: secret_name
  })
  secret = response.secret_string

  # Store secret in cache for future use
  cache_set(secret_name, secret)

  JSON.parse(secret)
end

# Initialize AWS client
region = 'your-region'
secrets_client = Aws::SecretsManager::Client.new(region: region)

# Retrieve the secret_name from environment variable
secret_name = ENV['SECRET_NAME']

# Retrieve the secret using the secret_name
secrets = get_secret(secrets_client, secret_name)
puts secrets

C# with Cache

dotnet add package AWSSDK.SecretsManager
dotnet add package AWSSDK.Extensions.NETCore.Setup
using System;
using System.Threading.Tasks;
using Amazon;
using Amazon.Extensions.NETCore.Setup;
using Amazon.SecretsManager;
using Amazon.SecretsManager.Extensions.Caching;

class Program
{
    private static readonly string region = "your-region";

    static async Task Main(string[] args)
    {
        var secretName = Environment.GetEnvironmentVariable("SECRET_NAME");
        var secret = await GetSecret(secretName);

        Console.WriteLine(secret);
    }

    private static async Task<string> GetSecret(string secretName)
    {
        var options = new AWSOptions
        {
            Region = RegionEndpoint.GetBySystemName(region)
        };

        using (var secretsManagerCache = new SecretsManagerCache(options))
        {
            // The SecretsManagerCache handles in-memory caching and retrieval
            var cachedSecret = await secretsManagerCache.GetSecretString(secretName);
            return cachedSecret;
        }
    }
}

Example Code Without Cache

Ruby Without Cache

require 'aws-sdk-secretsmanager'
require 'json'

def get_secret(secrets_client, secret_name)
  # Retrieve the secret from Secrets Manager
  response = secrets_client.get_secret_value({
    secret_id: secret_name
  })
  secret = response.secret_string

  # Parse the secret and remove from memory
  parsed_secret = JSON.parse(secret)
  secret.clear  # Clear the secret from memory
  parsed_secret
end

# Initialize AWS client
region = 'your-region'
secrets_client = Aws::SecretsManager::Client.new(region: region)

# Retrieve the secret_name from environment variable
secret_name = ENV['SECRET_NAME']

# Retrieve the secret using the secret_name
secrets = get_secret(secrets_client, secret_name)
puts secrets

C# Without Cache

dotnet add package AWSSDK.SecretsManager
dotnet add package AWSSDK.Extensions.NETCore.Setup
using System;
using System.Threading.Tasks;
using Amazon;
using Amazon.Extensions.NETCore.Setup;
using Amazon.SecretsManager;
using Amazon.SecretsManager.Extensions.Caching;

class Program
{
    private static readonly string region = "your-region";

    static async Task Main(string[] args)
    {
        var secretName = Environment.GetEnvironmentVariable("SECRET_NAME");
        var secret = await GetSecret(secretName);

        Console.WriteLine(secret);
    }

    private static async Task<string> GetSecret(string secretName)
    {
        var options = new AWSOptions
        {
            Region = RegionEndpoint.GetBySystemName(region)
        };

        using (var secretsManagerCache = new SecretsManagerCache(options))
        {
            // The SecretsManagerCache handles in-memory caching and retrieval
            var cachedSecret = await secretsManagerCache.GetSecretString(secretName);
            return cachedSecret;
        }
    }
}