From b5c706326ada2c0d213dd512842c5f677d9d94f9 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 19 May 2018 06:03:29 -0700 Subject: Upgrade to Ruby 2.4.4 Fixes that make this work: * A change in Ruby (https://github.com/ruby/ruby/commit/ce635262f53b760284d56bb1027baebaaec175d1) requires passing in the exact required length for OpenSSL keys and IVs. * Ensure the secrets.yml is generated before any prepended modules are loaded. This is done by renaming the `secret_token.rb` initializer to `01_secret_token.rb`, which is a bit ugly but involves the least impact on other files. --- config/initializers/01_secret_token.rb | 95 ++++++++++++++++++++++++++++++++++ config/initializers/secret_token.rb | 92 -------------------------------- config/settings.rb | 4 ++ 3 files changed, 99 insertions(+), 92 deletions(-) create mode 100644 config/initializers/01_secret_token.rb delete mode 100644 config/initializers/secret_token.rb (limited to 'config') diff --git a/config/initializers/01_secret_token.rb b/config/initializers/01_secret_token.rb new file mode 100644 index 00000000000..02bded43083 --- /dev/null +++ b/config/initializers/01_secret_token.rb @@ -0,0 +1,95 @@ +# This file needs to be loaded BEFORE any initializers that attempt to +# prepend modules that require access to secrets (e.g. EE's 0_as_concern.rb). +# +# Be sure to restart your server when you modify this file. + +require 'securerandom' + +# Transition material in .secret to the secret_key_base key in config/secrets.yml. +# Historically, ENV['SECRET_KEY_BASE'] takes precedence over .secret, so we maintain that +# behavior. +# +# It also used to be the case that the key material in ENV['SECRET_KEY_BASE'] or .secret +# was used to encrypt OTP (two-factor authentication) data so if present, we copy that key +# material into config/secrets.yml under otp_key_base. +# +# Finally, if we have successfully migrated all secrets to config/secrets.yml, delete the +# .secret file to avoid confusion. +# +def create_tokens + secret_file = Rails.root.join('.secret') + file_secret_key = File.read(secret_file).chomp if File.exist?(secret_file) + env_secret_key = ENV['SECRET_KEY_BASE'] + + # Ensure environment variable always overrides secrets.yml. + Rails.application.secrets.secret_key_base = env_secret_key if env_secret_key.present? + + defaults = { + secret_key_base: file_secret_key || generate_new_secure_token, + otp_key_base: env_secret_key || file_secret_key || generate_new_secure_token, + db_key_base: generate_new_secure_token, + openid_connect_signing_key: generate_new_rsa_private_key + } + + missing_secrets = set_missing_keys(defaults) + write_secrets_yml(missing_secrets) unless missing_secrets.empty? + + begin + File.delete(secret_file) if file_secret_key + rescue => e + warn "Error deleting useless .secret file: #{e}" + end +end + +def generate_new_secure_token + SecureRandom.hex(64) +end + +def generate_new_rsa_private_key + OpenSSL::PKey::RSA.new(2048).to_pem +end + +def warn_missing_secret(secret) + warn "Missing Rails.application.secrets.#{secret} for #{Rails.env} environment. The secret will be generated and stored in config/secrets.yml." +end + +def set_missing_keys(defaults) + defaults.stringify_keys.each_with_object({}) do |(key, default), missing| + if Rails.application.secrets[key].blank? + warn_missing_secret(key) + + missing[key] = Rails.application.secrets[key] = default + end + end +end + +def write_secrets_yml(missing_secrets) + secrets_yml = Rails.root.join('config/secrets.yml') + rails_env = Rails.env.to_s + secrets = YAML.load_file(secrets_yml) if File.exist?(secrets_yml) + secrets ||= {} + secrets[rails_env] ||= {} + + secrets[rails_env].merge!(missing_secrets) do |key, old, new| + # Previously, it was possible this was set to the literal contents of an Erb + # expression that evaluated to an empty value. We don't want to support that + # specifically, just ensure we don't break things further. + # + if old.present? + warn < e - warn "Error deleting useless .secret file: #{e}" - end -end - -def generate_new_secure_token - SecureRandom.hex(64) -end - -def generate_new_rsa_private_key - OpenSSL::PKey::RSA.new(2048).to_pem -end - -def warn_missing_secret(secret) - warn "Missing Rails.application.secrets.#{secret} for #{Rails.env} environment. The secret will be generated and stored in config/secrets.yml." -end - -def set_missing_keys(defaults) - defaults.stringify_keys.each_with_object({}) do |(key, default), missing| - if Rails.application.secrets[key].blank? - warn_missing_secret(key) - - missing[key] = Rails.application.secrets[key] = default - end - end -end - -def write_secrets_yml(missing_secrets) - secrets_yml = Rails.root.join('config/secrets.yml') - rails_env = Rails.env.to_s - secrets = YAML.load_file(secrets_yml) if File.exist?(secrets_yml) - secrets ||= {} - secrets[rails_env] ||= {} - - secrets[rails_env].merge!(missing_secrets) do |key, old, new| - # Previously, it was possible this was set to the literal contents of an Erb - # expression that evaluated to an empty value. We don't want to support that - # specifically, just ensure we don't break things further. - # - if old.present? - warn <