Read secrets using AWS secret manager in Rails

Storing secrets in a version control system is not a good idea. Rails provides a way to store credentials in an encrypted manner. In such approach, we need to just store secret key (called as master.key) separately which is used to decrypt credentials.

AWS has released a feature called AWS secret manager. It solves a few problems.

  1. Storing environment credentials in a secure manner
  2. Auto rotate secrets using AWS Lambda in an automated manner
  3. Storage and access of secrets in environment specific manner

Read more about AWS secrets here. To create secrets using AWS secrets manager follow this article

In this article, we will learn how to use secrets created using AWS secret manager and using those in the Rails application.

Step 1: Add aws-sdk-secretsmanager gem

To access services specific to AWS secret manager, add the gem in Rails application.

gem 'aws-sdk-secretsmanager'

Step 2: Create secret manager initializer

Create an initializer file in config/initializers directory. Let’s name it as secret_manager.rb

require 'aws-sdk-secretsmanager'

def set_aws_managed_secrets
  # secret name created in aws secret manager
  secret_name = "#{ENV['RAILS_ENV']}/repository_name/postgres/username"
  # region name
  region_name = 'ap-east-1'

  client = Aws::SecretsManager::Client.new(region: region_name)

  begin
    get_secret_value_response = client.get_secret_value(secret_id: secret_name)
  rescue Aws::SecretsManager::Errors::DecryptionFailure => e
    raise
  rescue Aws::SecretsManager::Errors::InternalServiceError => e
    raise
  rescue Aws::SecretsManager::Errors::InvalidParameterException => e
    raise
  rescue Aws::SecretsManager::Errors::InvalidRequestException => e
    raise
  rescue Aws::SecretsManager::Errors::ResourceNotFoundException => e
    raise
  else
    if get_secret_value_response.secret_string
      secret_json = get_secret_value_response.secret_string
      secret_hash = JSON.parse(secret_json)
      ENV['DATABASE_HOST'] = secret_hash['host']
      ENV['DATABASE_USERNAME'] = secret_hash['username']
      ENV['DATABASE_PASSWORD'] = secret_hash['password']
    end
  end
end

Below are a few noteworthy things:

We have created secrets specific to environment name in AWS secret manager. e. g.

staging/example/postgres/test_username

And the code below is used to define the secret name based on rails environment.

secret_name = "#{ENV['RAILS_ENV']}/repository_name/postgres/username"

We receive the json response in lines given below.

secret_json = get_secret_value_response.secret_string
secret_hash = JSON.parse(secret_json)

secret_hash is a Hash with values we have stored in aws secret manager.

Now, we just make those values available in ENV variable in lines given below.

ENV['DATABASE_HOST'] = secret_hash['host']
ENV['DATABASE_USERNAME'] = secret_hash['username']
ENV['DATABASE_PASSWORD'] = secret_hash['password']

3. Use secrets from ENV

Now, wherever we need those values in the codebase (e.g. config/database.yml), we can directly access them with ENV['DATABASE_HOST'], ENV['DATABASE_USERNAME'] and so on.

Posting sample database.yml file below.

# config/database.yml.sample

default: &default
  adapter: postgresql
  encoding: utf8
  username: "<%= ENV['DATABASE_USERNAME'] %>"
  password: "<%= ENV['DATABASE_PASSWORD'] %>"
  host: "<%= ENV['DATABASE_HOST'] %>"
  database: "<%= ENV['DATABASE_NAME'] %>"
  port: 5432

development:
  <<: *default

staging:
  <<: *default

test:
  <<: *default

production:
  <<: *default
akshay

Akshay Mohite

Hi there! I am a Ruby on Rails & ReactJS Enthusiast, building some cool products at DTree Labs.

Read More
Buy me a coffee