Skip to content

Commit

Permalink
Auto-generate env var values, e.g for SECRET_KEY_BASE (#440)
Browse files Browse the repository at this point in the history
When specifying `:env_vars` in the tomo config file, you can now use a
special `:generate_secret` value. This will automatically generate a
secret using `SecureRandom.hex(64)` the first time it is needed.

The tomo configuration template created by `tomo init` now includes this
env var value:

```
SECRET_KEY_BASE: :generate_secret
```

This means that the first time the app is deployed, an appropriate value
for `SECRET_KEY_BASE` will be generated automatically, without prompting
the user.
  • Loading branch information
mattbrictson authored Apr 12, 2024
1 parent 0b1d026 commit 4547f4e
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 20 deletions.
16 changes: 13 additions & 3 deletions docs/plugins/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ export RAILS_ENV=production
export PUMA_THREADS=20
```

For environment variables that are used for secrets or other sensitive data, you can specify `:prompt` instead of the actual value. In this case tomo will prompt interactively for the value the first time it is needed. For example:
#### `:prompt`

For environment variables that are used for API keys or other sensitive data, you can specify `:prompt` instead of the actual value. In this case tomo will prompt interactively for the value the first time it is needed. For example:

```ruby
set env_vars: { SECRET_KEY_BASE: :prompt }
set env_vars: { DATABASE_URL: :prompt }
```

The first time `env:update` is run, tomo will prompt for the value:
Expand All @@ -57,11 +59,19 @@ $ tomo deploy
tomo deploy v1.0.0
→ Connecting to [email protected]
• env:update
SECRET_KEY_BASE?
DATABASE_URL?
```

Once the environment variable exists in the envrc file, tomo will no longer prompt for it.

#### `:generate_secret`

Similarly, for environment variables that requires a randomly generated secret value, like `SECRET_KEY_BASE`, you can specify `:generate_secret`. In this case, tomo will generate a value using `SecureRandom.hex(64)` the first time it is needed.

```ruby
set env_vars: { SECRET_KEY_BASE: :generate_secret }
```

`env:update` is intended for use as a [deploy](../commands/deploy.md) task. It should be run at the beginning of a deploy to ensure that the environment has all the latest values before other tasks are run.

### env:set
Expand Down
5 changes: 1 addition & 4 deletions docs/tutorials/deploying-rails-from-scratch.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,7 @@ Tomo comes with a `setup` command that will prepare your VPS for its first deplo
$ tomo setup
```

You will be asked for two environment variables. Tomo will store these values on the VPS so that you only have to provide them once:

- **DATABASE_URL** is needed to tell Rails how to connect to the database. The basic app we are deploying uses sqlite. Provide this value when prompted: `sqlite3:/var/www/rails-new/shared/production.sqlite3`. This will store the database in a shared location so that it doesn't change from release to release.
- **SECRET_KEY_BASE** is needed by all Rails apps to securely encrypt session cookies and other important data. Run `ruby -rsecurerandom -e "puts SecureRandom.hex(64)"` to generate an appropriate value.
You will be asked to specify a value for the **DATABASE_URL** environment variable. Tomo will store this value on the VPS so that you only have to provide it once. Provide this value when prompted: `sqlite3:/var/www/rails-new/shared/production.sqlite3`.

Note that `tomo setup` compiles Ruby from source, which will take several minutes.

Expand Down
21 changes: 15 additions & 6 deletions lib/tomo/plugin/env/tasks.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "monitor"
require "securerandom"

module Tomo::Plugin::Env
class Tasks < Tomo::TaskLibrary # rubocop:disable Metrics/ClassLength
Expand All @@ -14,17 +15,25 @@ def setup
update
end

def update
def update # rubocop:disable Metrics/MethodLength
return if settings[:env_vars].empty?

modify_env_file do |env|
settings[:env_vars].each do |name, value|
next if value == :prompt && contains_entry?(env, name)
case value
when :prompt
next if contains_entry?(env, name)

value = prompt_for(name, message: <<~MSG)
The remote host needs a value for the $#{name} environment variable.
Please provide a value at the prompt.
MSG
when :generate_secret
next if contains_entry?(env, name)

value = SecureRandom.hex(64)
end

value = prompt_for(name, message: <<~MSG) if value == :prompt
The remote host needs a value for the $#{name} environment variable.
Please provide a value at the prompt.
MSG
replace_entry(env, name, value)
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/tomo/templates/config.rb.erb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ set env_vars: {
RUBY_YJIT_ENABLE: "1",
BOOTSNAP_CACHE_DIR: "tmp/bootsnap-cache",
DATABASE_URL: :prompt,
SECRET_KEY_BASE: :prompt
SECRET_KEY_BASE: :generate_secret
}
set linked_dirs: %w[
.yarn/cache
Expand Down
8 changes: 2 additions & 6 deletions test/e2e/rails_setup_deploy_e2e_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def teardown
end

def test_rails_setup_deploy
in_cloned_rails_repo do # rubocop:disable Metrics/BlockLength
in_cloned_rails_repo do
bundle_exec("tomo init")
config = File.read(".tomo/config.rb")
config.sub!(
Expand All @@ -32,11 +32,7 @@ def test_rails_setup_deploy
CONFIG
File.write(".tomo/config.rb", config)

bundle_exec(
"tomo run env:set " \
"DATABASE_URL=sqlite3:/var/www/rails-new/shared/production.sqlite3 " \
"SECRET_KEY_BASE=#{SecureRandom.hex(64)}"
)
bundle_exec("tomo run env:set DATABASE_URL=sqlite3:/var/www/rails-new/shared/production.sqlite3")
bundle_exec("tomo setup")
bundle_exec("tomo deploy")

Expand Down
34 changes: 34 additions & 0 deletions test/tomo/plugin/env/tasks_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,38 @@ def test_executes_chmod_to_reduce_visibility_of_envrc_file_upon_creation
tester.run_task("env:setup")
assert_equal("chmod 600 /app/envrc", tester.executed_scripts.last)
end

def test_setup_generates_a_random_value_for_var_marked_as_generate_secret
tester = Tomo::Testing::MockPluginTester.new(
"env",
settings: {
env_path: "/app/envrc",
env_vars: {
RAILS_ENV: "production",
SECRET_KEY_BASE: :generate_secret
}
}
)
tester.run_task("env:setup")
assert_match(/SECRET_KEY_BASE\\=\h{64}/, tester.executed_scripts[4])
end

def test_does_not_overwrite_previously_generated_secret
tester = Tomo::Testing::MockPluginTester.new(
"env",
settings: {
env_path: "/app/envrc",
env_vars: {
RAILS_ENV: "production",
SECRET_KEY_BASE: :generate_secret
}
}
)
tester.mock_script_result("cat /app/envrc", stdout: <<~STDOUT)
export SECRET_KEY_BASE=do-not-replace-me
STDOUT
tester.run_task("env:update")

assert_match(/SECRET_KEY_BASE\\=do-not-replace-me/, tester.executed_scripts[1])
end
end

0 comments on commit 4547f4e

Please sign in to comment.