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

Improve readme and add warning on rails credentials conflict #35

Merged
merged 1 commit into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ GEM
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
crass (1.0.6)
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
diff-lcs (1.5.0)
drb (2.2.0)
ruby2_keywords
Expand Down Expand Up @@ -126,6 +129,7 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
debug
eyaml!
fakefs
ffi (~> 1.15.5)
Expand Down
65 changes: 60 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ gem 'eyaml'

For MacOS users, libsodium is available via homebrew and can be installed with:
```shell
brew install libsodium
Wbrew install libsodium
```

## Usage
Expand Down Expand Up @@ -55,12 +55,13 @@ Options:
```shell
-> % eyaml encrypt config/secrets.production.eyaml
Wrote 517 bytes to config/secrets.production.eyaml.
```

Note: entries starting with an `_` won't be encrypted. This has to be the case for the `_public_key`, but can be handy if you want to add keys in there that you dont't want to encrypt. Like a public key for ex.
```

#### `eyaml decrypt`

Decrypts the provided EYAML file.
Decrypts the whole provided EYAML file.

```shell
-> % eyaml decrypt config/secrets.production.eyaml
Expand All @@ -78,7 +79,7 @@ _public_key: a3dbdef9efd1e52a34588de56a6cf9b03bbc2aaf0edda145cfbd9a6370a0a849
my_secret: 85d1fca99d98c4e7b83b868f75f809e1e33346317b0c354b593cdcdc8793ad4e
```

The private key must be saved in the default key directory (`/opt/ejson/keys`) with the filename being the public key and the contents, the private key, a key directory you'll provide later, or just pass the `--write` flag for `eyaml` to handle it for you.
The private key must be saved in the default key directory (`/opt/ejson/keys`) or the `EJSON_PRIVATE_KEY` must point to the right directory, with the filename being the public key and the contents, the private key, a key directory you'll provide later, or just pass the `--write` flag for `eyaml` to handle it for you.

```shell
-> % eyaml keygen
Expand All @@ -96,7 +97,12 @@ b01592942ba10f152bcf7c6b6734f6392554c578ff24cebcc62f9e3da6fcf302

### Rails

`eyaml` comes with baked in Rails support. It will search for a secrets or credentials file in `config/`, decrypt, and load the first valid one it finds.
`eyaml` comes with baked in Rails support.
It will search for a rails secrets or credentials file in `config/`, decrypt, and load the first valid one it finds.

For this a public-private keyfile needs to be present, which you can generate with `eyaml keygen`. For a development/test environment you can keep this in your
version control, but on production you want to take the proper precautions since this can contain your rails master key as well.

Credential files have priority over secrets before rails 7.2:
`credentials.{eyaml|eyml|ejson}` (e.g. `config/credentials.eyaml`) then `credentials.$env.{eyaml|eyml|ejson}` (e.g. `credentials.production.eyml`).
Then if no credentials are found it will look for a secrets file:
Expand All @@ -106,6 +112,55 @@ Note: From rails 7.2 onwards secrets are deprecated and eyaml will only look for

Instead of needing a private key locally, you can provide it to EYAML by setting `EJSON_PRIVATE_KEY` and it'll be automatically used for decrypting the secrets file.

If you put your rails master key encrypted in the eyaml file, make sure you don't have another `master.key` file somewhere, since that can interfere.

### Example setup

To add encryption + credentials to a rails project do the following things:

- Generate a private-public keypair with (or add the --write flag and a keypair file will be written to `/opt/ejson/keys/`):
```shell
eyaml keygen

Public Key: a3dbdef9efd1e52a34588de56a6cf9b03bbc2aaf0edda145cfbd9a6370a0a849
Private Key: b01592942ba10f152bcf7c6b6734f6392554c578ff24cebcc62f9e3da6fcf302
```

For this example I show you a dev setup, but for test, production etc. it works the same.

- Create a file with the name of the public key that contains the private key.
If you don't want to add the file to the `/opt/ejson/keys/` (for for example a dev/test environment) so you can check it in with your version management you can set the `EJSON_KEYDIR` to the keypair file
in rails `application.rb` like so:
```ruby
ENV["EJSON_KEYDIR"] = File.expand_path("../dev/ejson-keys", __dir__) unless Rails.env.production?
```
and rails will look there for the file decryption when the environment loads.
You can test this by calling
```ruby
Rails.application.credentials.secret_key_base
```
in a rails console and it should give you back the unencrypted key.

Note that you should not have a `config/master.key` file present (created by rails when using it's credentials management like for ex when calling `rails credentials:edit`) when using eyaml.
Eyaml is a replacement for rails's credentials management and currently conflicts with it. Eyaml will raise when a master.key is present.

- Create a `config/credentials.development.eyaml` file
- In the credentials file add:
```yaml
_public_key: a3dbdef9efd1e52a34588de56a6cf9b03bbc2aaf0edda145cfbd9a6370a0a849
```
on top
- You can then add your rails `secret_key_base` like so:
```yaml
secret_key_base: <secret>
```
And any other key you need in there.
- Then every time you edit your eyaml file(s) run (for ex for development):
```shell
eyaml encrypt config/credentials.development.eyaml
```
And you can see that the key put in there is encrypted afterwards (except the ones starting with an `_`).

### Apple M1 Support

If you're using the new Apple M1, you need to ensure that you're using a `ffi` that is working. We've temporarily been including a fork with a fix in any `Gemfile` where we've included `eyaml`:
Expand Down
1 change: 1 addition & 0 deletions eyaml.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ Gem::Specification.new do |spec|

spec.add_development_dependency("rake", "~> 13.0")
spec.add_development_dependency("rspec", "~> 3.0")
spec.add_development_dependency("debug")
end
6 changes: 6 additions & 0 deletions lib/eyaml/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ module Rails
class Railtie < Rails::Railtie
PRIVATE_KEY_ENV_VAR = "EJSON_PRIVATE_KEY"

class ConflictError < StandardError
end

config.before_configuration do
if File.exist?(Rails.root.join("config", "master.key"))
raise ConflictError, "A config/master.key has been found. The rails credentials lookup conflicts with eyaml. Please remove rails credentials management by removing the master.key file to keep using eyaml."
end
secret_files_present = Dir.glob(auth_files(:secrets)).any?
credential_files_present = Dir.glob(auth_files(:credentials)).any?

Expand Down
14 changes: 14 additions & 0 deletions spec/eyaml/railtie_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@
allow_rails.to(receive_message_chain("application.credentials").and_return(credentials))
end

after do
if File.exist?(config_root.join("master.key"))
File.delete(config_root.join("master.key"))
end
end

it "raises when a master.key file is present" do
run_load_hooks
expect(credentials).to(include(:secret))

File.write(config_root.join("master.key"), "test")
expect { run_load_hooks }.to raise_error(EYAML::Rails::Railtie::ConflictError)
end

it "merges credentials into application credentials" do
run_load_hooks
expect(credentials).to(include(:secret))
Expand Down
Loading