Rails 5 not loading encrypted secrets in production.rb - ruby-on-rails

I have a project configured to use Rails encrypted secrets. Everything works fine until I try to access a secret within the production.rb environment file.
I found that if I try to access something like Rails.application.secrets.smtp_user_name within the configure block it wipes out all of the encrypted secrets (I'm only left with things in secrets.yml ... which I'm not using). Example:
Loading production environment (Rails 5.1.2)
irb(main):001:0> Rails.application.secrets
=> {:secret_key_base=>nil, :secret_token=>nil}
If I remove the attempt to access the secrets it works correctly:
irb(main):001:0> Rails.application.secrets
=> {:secret_key_base=>"...", :smtp_user_name=>"...", :smtp_password=>"...", :secret_token=>nil}
I'm currently working around it by using two configure blocks in production.rb as follows:
# This is hacky, it needs to come before the second configure block where
# the encrypted secrets are used.
Rails.application.configure do
config.read_encrypted_secrets = true
end
Rails.application.configure do
... stuff that uses Rails.application.secrets, like ActionMailer
end
Anybody else faced this and possibly have a more correct way to work around it?
It makes sense why this is happening (Rails doesn't know to load the encrypted secrets because we haven't told it to you), but I'm thinking there must be a better way to deal with it.
Update
This nailed me again 9 months later. To be clear, if you reference Rails.application.secrets BEFORE calling config.read_encrypted_secrets = true you will cache empty secrets and not be able to access any of the values in secrets.yml.enc!
In my case I had tried to configure Paperclip S3 credentials in application.rb while my config.read_encrypted_secrets = true was set in production.rb. Result was devise.rb blowing up trying to read a secret for the key base, all because in application.rb I had effectively cached nil secrets.

here is bug report related to your issue:
https://github.com/rails/rails/issues/30362#issuecomment-326821656
In general even if you have all things set up properly you need check also order how your application loads secrets. If your application ask first for Rails.application.secrets and then set proper flag... Rails.application.secrets will cache version without secrets... and Secrets from secret.yml.enc will not be merge.

Related

Host Rails App in the Google Cloud App Engine and set Environment variables

I will host my RoR API-App in the Google App-Engine.
Everything works so far, but I have to store usernames, passwords and keys (e.g. Database user/password) in plain text in the app.yaml. this is just stupid, so I will never be able to push this to my git repo! Usually I store stuff like this in an env variable and use them in my application.
But I did not find a way to set or access env variables.
Is there a way or an alternative to do so?
I did it!
For local development, I just set my env as usual.
If the mode is Production I load them from the Google Datastore all key values pairs and set them as an env variable.
I do this in an Initializer, to do so just create a file in YourApp/config/initializers/ and put the code in it! Just Create new Entities copy the Kind name in the code and set your project id. As your App is hosted in Google it should have access to the datastore (You need to set the right in the IAM-Manager)
require "google/cloud/datastore"
# Load the enviroment variables from the google datastore!
if Rails.env == "production"
data_store = Google::Cloud::Datastore.new(
project_id: 'YOUR_PROJECT_ID'
)
query = data_store.query "YOUR_KIND_NAME"
results = data_store.run query
puts "Set custom env variables!"
# Set each result as an env variable
results[0].properties.to_h.each do |key, value|
ENV[key]= value
end
end
As mentioned in Best practices for managing credentials, you may use an environment variable pointing to credentials outside of the application's source code, such as Cloud Key Management Service. I also recommend to take a look at Secret management with Cloud KMS documentation which explains solutions when you choose a secret management.

Where do you save the Aws.config file for the aws-skd gem?

I am trying to install the aws-skd gem:
https://github.com/aws/aws-sdk-ruby
They suggest either ENV variables or their shared config file. I have already gone down the path of secrets.yml and it works nicely so far. So then I figured I could just use Aws.config which they state takes precedence over the former. I took a guess and placed it in the config folder but that does not work.
You should be able to just call s3 = Aws::S3::Client.new but in my case the default region and credentials are not being stored / applied.
Where do I store this file or is there another way to do this?
PS I tried adding it in an initializer but that didn't work either.
UPDATE
Now that I RTFM a few more times I found my issue. I will answer my own question below.
As stated in the documenation: ~/.aws/credentials You can read more details here.
So for some reason I read the docs as Aws.config being an actual file vs setting the config (D'oh!).
So I tried an initializer again:
#config/initalizers/aws-sdk.rb
require 'aws-sdk'
Aws.config.update({
access_key_id: Rails.application.secrets.aws_access_key_id,
secret_access_key: Rails.application.secrets.aws_secret_access_key,
region: Rails.application.secrets.aws_region
})
I actual had tired this before but I thought it was not working. I did not actually quit and restart the console as well as the server - I just reloaded the console so the initializer changes did not get reflected. +1 to #mark-b for making me read the docs again.

RoR, Fog: Getting Started with Fog

I just installed the gem asset_sync and I am trying to get set up with my AWS account. When I run bundle exec rake assets:precompile I get the following errror:
AssetSync::Config::Invalid: Fog provider can't be blank, Fog directory can't be blank
I understand the simply reason that I am getting this error, namely that I havent pushed the Fog provider or directory to heroku. What I am stumped about is where to put the Following code (Taken from the Fog README). In config/initializers/fog.rb? Is this all I need to do to start using fog, other than installing the gem?
require 'rubygems'
require 'fog'
# create a connection
connection = Fog::Storage.new({
:provider => 'AWS',
:aws_access_key_id => YOUR_AWS_ACCESS_KEY_ID,
:aws_secret_access_key => YOUR_AWS_SECRET_ACCESS_KEY
})
# First, a place to contain the glorious details
directory = connection.directories.create(
:key => "fog-demo-#{Time.now.to_i}", # globally unique name
:public => true
)
not a problem, getting started tends to be the hardest part.
The answer is, it depends. I'd actually venture to say it would be best to put this in your environment based initializers, ie config/init/development or config/init/production, etc. Relatedly, you probably will not want to generate a new directory every time you start your app (there is an account level limit of 100 total I believe). So you might want to either set a key for each environment for that create or simply create the directory somewhere outside the initializers (and within the initializer you can assume it exists).
If you want to use that directory directly, you'll still need to create a reference, but you can create a local reference without making any api calls with #new like this:
directory = connection.directories.new(:key => ...)
As for asset_sync, it needs those keys and a reference to the directory key, which you will probably want to provide via ENV vars (to avoid checking your credentials into version control). You can find details on which keys and how to set them here: https://github.com/rumblelabs/asset_sync#built-in-initializer-environment-variables (the readme also describes how to do it via initializers, but that probably isn't the best plan).
Hope that helps!

ActionMailer password security

Am I crazy, or is it a bad idea to keep my SMTP username and password for ActionMailer in the actual (development/production) config file? It seems like I should store it an encrypted place, or at the very minimum, exclude it from my Mercurial pushes.
Right now, I'm just removing the password from my source file before performing a push, but there's got to be a smarter way than the one I'm using. :)
Perhaps I should store it in my database as another user (which is already stored with encrypted passwords) and fetch it programatically?
Use an application configuration file that is not stored in your repository for storing sensitive information. Here is how I've done it:
Add an app_config.yml in your config directory. Its contents would look like this:
smtp_password: kl240jvfslkr32rKgjlk
some_other_password: 34hg9r0j0g402jg
and_so_on: lkn$#gJkjgsFLK4gaj
Add a preinitializer.rb in your config directory with the following contents:
require 'yaml'
APP_CONFIG = YAML.load(File.read(RAILS_ROOT + "/config/app_config.yml"))
Substitute your passwords for values in the APP_CONFIG variable, like so:
smtp_password = kl240jvfslkr32rKgjlk # old version
smtp_password = APP_CONFIG['smtp_password'] # new version
Make sure you don't include app_config.yml in your repository, though you may want to create an example file that is checked in, just to show a sample of what should be in it. When you deploy your application, make sure that app_config.yml is stored on the server. If you're using a standard Capistrano deployment, put the file in the shared folder and update your deployment task to create a symlink to it in the current release's directory.
Jimmy's answer is perfect (+1), I would also note that Github has recommended .gitignore files for every language and the Rails one is here Note that it includes config/*.yml so that no config/yml file is in the respository to begin with. Probably a good move.
Use Capistrano to ask for these things upon deploy:setup the same way you should be doing for your database stuff:
task :my_silly_task do
sendgrid_password = Capistrano::CLI.password_prompt("Sendgrid password: ")
require 'yaml'
spec = {... whatever yaml you need -- probably what Jimmy said...}
run "mkdir -p #{shared_path}/config"
put(spec.to_yaml, "#{shared_path}/config/mailer_config.yml")
end

What is the best way to store app specific configuration in rails?

I need to store app specific configuration in rails. But it has to be:
reachable in any file (model, view, helpers and controllers
environment specified (or not), that means each environment can overwrite the configs specified in environment.rb
I've tried to use environment.rb and put something like
USE_USER_APP = true
that worked to me but when trying to overwrite it in a specific environment it wont work because production.rb, for instance, seems to be inside the Rails:Initializer.run block.
So, anyone?
Look at Configatron: http://github.com/markbates/configatron/tree/master
I have yet to use it, but he's actively developing it now, and looks quite nice.
I was helping a friend set up the solution mentioned by Ricardo yesterday. We hacked it a bit by loading the YAML file with something similar to this (going from memory here):
require 'ostruct'
require 'yaml'
require 'erb'
#config = OpenStruct.new(YAML.load_file("#{RAILS_ROOT}/config/config.yml"))
config = OpenStruct.new(YAML.load(ERB.new(File.read("#{RAILS_ROOT}/config/config.yml")).result))
env_config = config.send(RAILS_ENV)
config.common.update(env_config) unless env_config.nil?
::AppConfig = OpenStruct.new(config.common)
This allowed him to embed Ruby code in the config, like in Rhtml:
development:
path_to_something: <%= RAILS_ROOT %>/config/something.yml
The most basic thing to do is to set a class variable from your environment.rb. I've done this for Google Analytics. Essentially I want a different key depending on which environment I'm in so development or staging don't skew the metrics.
This is how I did it.
In lib/analytics/google_analytics.rb:
module Analytics
class GoogleAnalytics
##account_id = nil
cattr_accessor :account_id
end
end
And then in environment.rb or in environments/production.rb or any of the other environment files:
Analytics::GoogleAnalytics.account_id = "xxxxxxxxx"
Then anywhere you ned to reference, say the default layout with the Google Analytics JavaScript, it you just call Analytics::GoogleAnalytics.account_id.
I found a good way here
Use environment variables. Heroku uses this. Remember that if you keep configuration in the codebase, anyone with access to the code has access to any secret configuration (aws api keys, gateway api keys, etc).
daemontool's envdir is a good tool for setting configuration, I'm pretty sure that's what Heroku uses to give application their environment variables.
I have used Rails Settings Cached.
It is very simple to use, keeps your configuration values cached and allows you to change them dynamically.

Resources