Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 341 modernise secret handling #392

Merged
merged 31 commits into from
Aug 25, 2023
Merged

Conversation

eoinkelly
Copy link
Contributor

Closes #341

  • Load non-sensitive app config from config/app.yml
    • Put explanatory comment at top of config/app.yml to explain that
      non-sensitive config should go here and sensitive config should go in
      config/secrets.yml
    • Update existing references to mail_from config item in the app
  • Remove Rails encrypted credentials files to avoid confusion
    • We have chosen not to use Rails encrypted credentials so we remove the
      files generated by Rails to avoid confusion.
  • Stop creating .env from example.env because bin/setup already does this
  • Modernise generated secrets.yml
    • Stop generating both secrets.yml and secrets.example.yml. Our secrets.yml
      loads everything from the ENV so it is safe to check in and having a
      separate example file is unnecessary.
    • Add secrets demonstrating how to configure ActiveRecord encrypted attributes
      without using Rails encrypted credentials.
  • Remove confusing comment about .envrc at top of .env file.

* Stop generating both `secrets.yml` and `secrets.example.yml`. Our `secrets.yml`
  loads everything from the ENV so it is safe to check in and having a
  separate example file is unnecessary.
* Add secrets demonstrating how to configure ActiveRecord encrypted attributes
  without using Rails encrypted credentials.
We have chosen not to use Rails encrypted credentials so we remove the
files generated by Rails to avoid confusion.
* Put explanatory comment at top of `config/app.yml` to explain that
  non-sensitive config should go here and sensitive config should go in
  `config/secrets.yml`
* Update existing references to `mail_from` config item in the app
@eoinkelly eoinkelly marked this pull request as ready for review October 22, 2022 21:33
@@ -19,4 +19,4 @@
# Configure the default mailer to use the our default from address
gsub_file "app/mailers/application_mailer.rb",
"default from: 'from@example.com'",
"default from: Rails.application.secrets.mail_from"
"default from: Rails.application.config.app.mail_from"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot load things directly in to Rails.application.config using a YAML file - we have to pick a namespace under config. I chose config.app but totally open to changing this.

Alternatively we could skip the YAML file completely and just have a ruby initializer which loads things directly into Rails.application.config. I slightly favour the YAML file because it makes it easier when secrets vary between environments.

Comment on lines +28 to +30
config.active_record.encryption.primary_key = Rails.application.secrets.active_record_encryption_primary_key
config.active_record.encryption.deterministic_key = Rails.application.secrets.active_record_encryption_deterministic_key
config.active_record.encryption.key_derivation_salt = Rails.application.secrets.active_record_encryption_key_derivation_salt
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@G-Rath It turns out we can use encrypted attributes without encrypted credentials. It is very poorly documented so I think it's worth baking into the template.

secret_key_base: "<%= ENV['RAILS_SECRET_KEY_BASE'] %>"
active_record_encryption_primary_key: "<%= ENV['ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY'] %>"
active_record_encryption_deterministic_key: "<%= ENV['ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY'] %>"
active_record_encryption_key_derivation_salt: "<%= ENV['ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT'] %>"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you nest keys here then you have to use hash key syntax to get the value e.g.

aaa:
  bb: "foo"

# Rails.application.secrets.aaa.bb # breaks
# Rails.application.secrets.aaa[:bb] # works

This felt a bit ugly so I flattened the secrets instead

variants/backend-base/config/secrets.yml Outdated Show resolved Hide resolved
variants/backend-base/example.env.tt Outdated Show resolved Hide resolved
variants/devise/template.rb Outdated Show resolved Hide resolved
Comment on lines +7 to +8
remove_file "config/master.key"
remove_file "config/credentials.yml.enc"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We aren't using these but by default Rails creates them and puts a secret_key_base value in there which will be preferred by Rails if it exists and can be decrypted.

@eoinkelly eoinkelly requested review from G-Rath, joshmcarthur and a team October 22, 2022 21:40
Comment on lines 28 to 32
# to create new versions of these secrets for each deployed environment (e.g.
# staging, production)
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="PLACEHOLDER_PRIMARY_KEY"
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="PLACEHOLDER_DETERMINISTIC_KEY"
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="PLACEHOLDER_KEY_DERIVATION_SALT"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These env vars need to exist to load Rails because we are loading them with ENV.fetch. We need to run Rails to set realistic values so we need to set these env vars to an initial placeholder value and later set better values.


gsub_file("example.env", "PLACEHOLDER_PRIMARY_KEY", db_secrets.fetch("primary_key"))
gsub_file("example.env", "PLACEHOLDER_DETERMINISTIC_KEY", db_secrets.fetch("deterministic_key"))
gsub_file("example.env", "PLACEHOLDER_KEY_DERIVATION_SALT", db_secrets.fetch("key_derivation_salt"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're using generated ones here, i wonder if we should add these keys to the initializer that checks for using the example.env secret key base. #313

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, will do.

Comment on lines 1 to 42
class VerifyPlaceholderSecretsNotUsedForReal
class << self
PLACEHOLDER_PREFIX_REGEX = /(PLACEHOLDER|FAILED_TO_GENERATE)/.freeze

# RAILS_SECRET_KEY_BASE should be set to something other than its value in example.env
def run
return if local?

if Rails.env.production? && Rails.root.join("example.env").read.include?(ENV.fetch("RAILS_SECRET_KEY_BASE"))
fail "RAILS_SECRET_KEY_BASE is unchanged from example.env. " \
"Generate a new one with `bundle exec rails secret`"
verify_secret_key_base
verify_activerecord_encryption_secrets
end

private

def verify_secret_key_base
return unless Rails.root.join("example.env").read.include?(ENV.fetch("RAILS_SECRET_KEY_BASE"))

fail "RAILS_SECRET_KEY_BASE is unchanged from example.env. Generate a new one with `bundle exec rails secret`"
end

##
# Verify that placeholder values created by the Ackama rails template are
# not being used for real.
#
def verify_activerecord_encryption_secrets # rubocop:disable Metrics/AbcSize
secrets = [
Rails.application.config.active_record.encryption.primary_key,
Rails.application.config.active_record.encryption.deterministic_key,
Rails.application.config.active_record.encryption.key_derivation_salt
]

secrets.each do |secret|
fail "Insecure ENV: ActiveRecored encrypted credentials env contain in insecure placeholder value." if secret.match?(PLACEHOLDER_PREFIX_REGEX)
end
end

def local?
Rails.env.development? || Rails.env.test?
end
end
end

VerifyPlaceholderSecretsNotUsedForReal.run
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@robotdana You may want to review this because I've changed it quite a bit

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good

@eoinkelly eoinkelly requested a review from robotdana May 6, 2023 00:52
variants/backend-base/config/app.yml Outdated Show resolved Hide resolved
variants/backend-base/config/initializers/check_env.rb Outdated Show resolved Hide resolved
variants/backend-base/config/initializers/check_env.rb Outdated Show resolved Hide resolved
Comment on lines 1 to 2
# Do NOT put secrets directly into this file. All secrets should be loaded from ENV!
# Be sure to restart your server when you modify this file.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐘 I think it would be worth mentioning config/app.yml here

variants/backend-base/example.env.tt Outdated Show resolved Hide resolved
Comment on lines +20 to +21
staging:
<<: *default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐘 ideally we should actually only add this if a capistrano variant is enabled, because Heroku doesn't do staging so it isn't reasonable to assume all apps will have it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep as is because:

  1. Heroku is the odd one out in not supporting a staging env out of the box - every other way of deploying Rails uses one AFAIK (not just capistrano based deploys).
  2. Having this here doesn't harm a Heroku deploy.
  3. We don't currently have a template config variable for "deploying to heroku". I don't think this use-case is enough to add that complexity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that can be the case because Rails/UnknownEnv does not include staging by default, but its a while since I looked at this so can't remember.

I'm fine with whatever you think is worth doing

Comment on lines +22 to +23
staging:
<<: *default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐘 ideally we should actually only add this if a capistrano variant is enabled, because Heroku doesn't do staging so it isn't reasonable to assume all apps will have it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep as is because:

  1. Heroku is the odd one out in not supporting a staging env out of the box - every other way of deploying Rails uses one AFAIK (not just capistrano based deploys).
  2. Having this here doesn't harm a Heroku deploy.
  3. We don't currently have a template config variable for "deploying to heroku". I don't think this use-case is enough to add that complexity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that can be the case because Rails/UnknownEnv does not include staging by default, but its a while since I looked at this so can't remember.

I'm fine with whatever you think is worth doing

@eoinkelly eoinkelly requested a review from G-Rath July 23, 2023 20:53
Comment on lines +20 to +21
staging:
<<: *default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that can be the case because Rails/UnknownEnv does not include staging by default, but its a while since I looked at this so can't remember.

I'm fine with whatever you think is worth doing

Comment on lines +22 to +23
staging:
<<: *default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that can be the case because Rails/UnknownEnv does not include staging by default, but its a while since I looked at this so can't remember.

I'm fine with whatever you think is worth doing

@lukeify
Copy link

lukeify commented Aug 25, 2023

Merging as discussed at our Ruby catchup.

@eoinkelly eoinkelly merged commit b06d8bf into main Aug 25, 2023
22 checks passed
@eoinkelly eoinkelly deleted the issue-341-modernise-secrets branch August 25, 2023 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Should we still be generating config/secrets.yml
4 participants