diffcrypt 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +72 -5
- data/.rubocop.yml +7 -0
- data/CHANGELOG.md +62 -0
- data/Gemfile +3 -1
- data/README.md +15 -11
- data/Rakefile +3 -1
- data/SECURITY.md +17 -0
- data/diffcrypt.gemspec +2 -2
- data/lib/diffcrypt.rb +1 -0
- data/lib/diffcrypt/cli.rb +17 -8
- data/lib/diffcrypt/encryptor.rb +9 -9
- data/lib/diffcrypt/file.rb +73 -0
- data/lib/diffcrypt/rails/application_helper.rb +39 -0
- data/lib/diffcrypt/rails/encrypted_configuration.rb +27 -8
- data/lib/diffcrypt/railtie.rb +14 -0
- data/lib/diffcrypt/tasks/rails.rake +36 -0
- data/lib/diffcrypt/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dacd30e3ddee50fd84847385d2cd4161645b71b1cda79566557dff60e1f15108
|
4
|
+
data.tar.gz: 648e5cf57c6b909cf84196ded1643aa41baf776b8428862d57804d6465ebb3f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c20572e67d7416fd2fac81556eadba73555959273f7ac900a2d05c82c4799135edb9940a69659eca121cddfee4dd99b3a1b94e7b5effeeea491677ae638b2b7
|
7
|
+
data.tar.gz: 7e8fe375714cdfcb730792285f648a8a7f1164fe9015b42ee06a0a592b065a576a4059ba0d338f9ec79a0749b087d186c73e1d5ba89c6cecaebb4123490eabdf
|
data/.circleci/config.yml
CHANGED
@@ -1,11 +1,78 @@
|
|
1
1
|
version: 2.1
|
2
2
|
|
3
3
|
jobs:
|
4
|
-
|
4
|
+
bundle:
|
5
5
|
docker:
|
6
|
-
- image: circleci/ruby:2.
|
6
|
+
- image: circleci/ruby:2.7.1
|
7
|
+
working_directory: /mnt/ramdisk
|
7
8
|
steps:
|
8
9
|
- checkout
|
9
|
-
-
|
10
|
-
|
11
|
-
|
10
|
+
- restore_cache:
|
11
|
+
keys:
|
12
|
+
- bundler-{{ checksum "diffcrypt.gemspec" }}-{{ .Environment.CACHE_VERSION }}
|
13
|
+
- run:
|
14
|
+
name: bundle install
|
15
|
+
command: |
|
16
|
+
gem install bundler -v '2.1.4'
|
17
|
+
bundle install --path=vendor/bundle --jobs=4 --retry=3
|
18
|
+
- save_cache:
|
19
|
+
key: bundler-{{ checksum "diffcrypt.gemspec" }}-{{ .Environment.CACHE_VERSION }}
|
20
|
+
paths:
|
21
|
+
- vendor/bundle
|
22
|
+
|
23
|
+
test:
|
24
|
+
docker:
|
25
|
+
- image: circleci/ruby:2.7.1
|
26
|
+
working_directory: /mnt/ramdisk
|
27
|
+
steps:
|
28
|
+
- checkout
|
29
|
+
- restore_cache:
|
30
|
+
keys:
|
31
|
+
- bundler-{{ checksum "diffcrypt.gemspec" }}-{{ .Environment.CACHE_VERSION }}
|
32
|
+
- run:
|
33
|
+
name: bundle install
|
34
|
+
command: |
|
35
|
+
gem install bundler -v '2.1.4'
|
36
|
+
bundle install --path=vendor/bundle --jobs=4 --retry=3
|
37
|
+
- run:
|
38
|
+
name: Setup Code Climate test-reporter
|
39
|
+
command: |
|
40
|
+
# download test reporter as a static binary
|
41
|
+
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
42
|
+
chmod +x ./cc-test-reporter
|
43
|
+
- run:
|
44
|
+
name: rake test
|
45
|
+
command: |
|
46
|
+
./cc-test-reporter before-build
|
47
|
+
bundle exec rake test
|
48
|
+
./cc-test-reporter after-build --coverage-input-type lcov --exit-code $?
|
49
|
+
rubocop:
|
50
|
+
docker:
|
51
|
+
- image: circleci/ruby:2.7.1
|
52
|
+
working_directory: /mnt/ramdisk
|
53
|
+
steps:
|
54
|
+
- checkout
|
55
|
+
- restore_cache:
|
56
|
+
keys:
|
57
|
+
- bundler-{{ checksum "diffcrypt.gemspec" }}-{{ .Environment.CACHE_VERSION }}
|
58
|
+
- run:
|
59
|
+
name: bundle install
|
60
|
+
command: |
|
61
|
+
gem install bundler -v '2.1.4'
|
62
|
+
bundle install --path=vendor/bundle --jobs=4 --retry=3
|
63
|
+
- run:
|
64
|
+
name: rubocop
|
65
|
+
command: bundle exec rubocop
|
66
|
+
when: always
|
67
|
+
|
68
|
+
workflows:
|
69
|
+
version: 2
|
70
|
+
all:
|
71
|
+
jobs:
|
72
|
+
- bundle
|
73
|
+
- test:
|
74
|
+
requires:
|
75
|
+
- bundle
|
76
|
+
- rubocop:
|
77
|
+
requires:
|
78
|
+
- bundle
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
AllCops:
|
2
2
|
NewCops: enable
|
3
|
+
TargetRubyVersion: 2.6
|
3
4
|
|
4
5
|
Style/ClassAndModuleChildren:
|
5
6
|
Exclude:
|
@@ -9,8 +10,14 @@ Style/Documentation:
|
|
9
10
|
Metrics/MethodLength:
|
10
11
|
Exclude:
|
11
12
|
- test/**/*_test.rb
|
13
|
+
Style/TrailingCommaInArrayLiteral:
|
14
|
+
EnforcedStyleForMultiline: consistent_comma
|
12
15
|
Style/TrailingCommaInArguments:
|
13
16
|
EnforcedStyleForMultiline: consistent_comma
|
17
|
+
Style/TrailingCommaInHashLiteral:
|
18
|
+
EnforcedStyleForMultiline: consistent_comma
|
19
|
+
Style/AccessorGrouping:
|
20
|
+
EnforcedStyle: separated
|
14
21
|
|
15
22
|
Layout/LineLength:
|
16
23
|
Exclude:
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
|
9
9
|
|
10
|
+
## [0.5.0] - 2020-12-06
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- `Diffcrypt::Rails::ApplicationHelper` to simplify integration
|
15
|
+
- `rake diffcrypt:init` command to help setup credentials
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
|
19
|
+
- Default cipher is now `aes-265-gcm`
|
20
|
+
- YAML keys are now sorted
|
21
|
+
- Improved support for rails native `aes-128-gcm` cipher
|
22
|
+
- Improved test coverage for differing ciphers
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
## [0.4.1] - 2020-10-06
|
27
|
+
|
28
|
+
### Fixed
|
29
|
+
|
30
|
+
- Could not initialize a new file or modify existing rails format file (Thanks @swiknaba #31)
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
## [0.4.0] - 2020-10-01
|
35
|
+
|
36
|
+
### Changed
|
37
|
+
|
38
|
+
- Encryptor can now use other ciphers than the default
|
39
|
+
|
40
|
+
### Dependencies
|
41
|
+
|
42
|
+
- simplecov: 0.17.0 -> 0.18.0 (#20)
|
43
|
+
- rubocop: 0.88.0 -> 0.92.0 (#24)
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
## [0.3.3] - 2020-07-25
|
48
|
+
|
49
|
+
### Fixed
|
50
|
+
|
51
|
+
- Explicit FileUtils require to avoid potentially warning logs
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
## [0.3.2] - 2020-07-20
|
56
|
+
|
57
|
+
### Added
|
58
|
+
|
59
|
+
- CLI: `diffcrypt generate-key` command to generate a new key for a cipher
|
60
|
+
- Internal: Library now generates and publishes code coverage publically on Code Climate
|
61
|
+
|
62
|
+
### Changed
|
63
|
+
|
64
|
+
- Only support ruby 2.5+ since 2.4 is no longer maintained
|
65
|
+
|
66
|
+
### Removed
|
67
|
+
|
68
|
+
- No longer generate and store a checksum. Backwards compatible since it wasn't used
|
69
|
+
|
70
|
+
|
71
|
+
|
10
72
|
## [0.3.1] - 2020-07-08
|
11
73
|
|
12
74
|
### Fixed
|
data/Gemfile
CHANGED
@@ -7,4 +7,6 @@ gemspec
|
|
7
7
|
|
8
8
|
gem 'minitest', '~> 5.0'
|
9
9
|
gem 'rake', '~> 13.0'
|
10
|
-
gem 'rubocop', '~>
|
10
|
+
gem 'rubocop', '~> 1.5.2'
|
11
|
+
gem 'simplecov', '~> 0.20.0', require: false # CodeClimate not compatible with 0.18+ yet - https://github.com/codeclimate/test-reporter/issues/413
|
12
|
+
gem 'simplecov-lcov', '< 0.8'
|
data/README.md
CHANGED
@@ -36,8 +36,8 @@ There are a few ways to use the library, depending on how advanced your use case
|
|
36
36
|
The easiest way to get started is to use the CLI.
|
37
37
|
|
38
38
|
```shell
|
39
|
-
diffcrypt decrypt -k $(cat test/fixtures/
|
40
|
-
diffcrypt encrypt -k $(cat test/fixtures/
|
39
|
+
diffcrypt decrypt -k $(cat test/fixtures/aes-128-gcm.key) test/fixtures/example.yml.enc
|
40
|
+
diffcrypt encrypt -k $(cat test/fixtures/aes-128-gcm.key) test/fixtures/example.yml
|
41
41
|
```
|
42
42
|
|
43
43
|
|
@@ -66,23 +66,27 @@ Currently there is not native support for rails, but ActiveSupport can be monkey
|
|
66
66
|
the built in encrypter. All existing `rails credentials:edit` also work with this method.
|
67
67
|
|
68
68
|
```ruby
|
69
|
-
|
69
|
+
# config/application.rb
|
70
70
|
module Rails
|
71
71
|
class Application
|
72
|
-
|
73
|
-
Diffcrypt::Rails::EncryptedConfiguration.new(
|
74
|
-
config_path: Rails.root.join(path),
|
75
|
-
key_path: Rails.root.join(key_path),
|
76
|
-
env_key: env_key,
|
77
|
-
raise_if_missing_key: config.require_master_key,
|
78
|
-
)
|
79
|
-
end
|
72
|
+
include Diffcrypt::Rails::ApplicationHelper
|
80
73
|
end
|
81
74
|
end
|
82
75
|
```
|
83
76
|
|
84
77
|
|
85
78
|
|
79
|
+
## Converting between ciphers
|
80
|
+
|
81
|
+
Sometimes you may want to rotate the cipher used on a file. You can do this programmatically using the ruby code above, or you can also chain the CLI commands like so:
|
82
|
+
|
83
|
+
```shell
|
84
|
+
diffcrypt decrypt -k $(cat test/fixtures/aes-128-gcm.key) test/fixtures/example.yml.enc > test/fixtures/example.128.yml \
|
85
|
+
&& diffcrypt encrypt --cipher aes-256-gcm -k $(cat test/fixtures/aes-256-gcm.key) test/fixtures/example.128.yml > test/fixtures/example.256.yml.enc && rm test/fixtures/example.128.yml
|
86
|
+
```
|
87
|
+
|
88
|
+
|
89
|
+
|
86
90
|
## Development
|
87
91
|
|
88
92
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Rakefile
CHANGED
data/SECURITY.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
## Supported Versions
|
6
|
+
|
7
|
+
Since the internal APIs may change dramatically until v1.0, here is a list of the versions that are supported.
|
8
|
+
|
9
|
+
| Version | Supported |
|
10
|
+
| ------- | ------------------ |
|
11
|
+
| 0.3.x | :white_check_mark: |
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
## Reporting a Vulnerability
|
16
|
+
|
17
|
+
Please email security@marcqualie.com to report any security issues.
|
data/diffcrypt.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = 'Diffable encrypted configuration files that can be safely committed into a git repository'
|
13
13
|
spec.homepage = 'https://github.com/marcqualie/diffcrypt'
|
14
14
|
spec.license = 'MIT'
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 2.
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0')
|
16
16
|
|
17
17
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
18
18
|
|
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
|
23
23
|
# Specify which files should be added to the gem when it is released.
|
24
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
-
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
spec.files = Dir.chdir(::File.expand_path(__dir__)) do
|
26
26
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
27
27
|
end
|
28
28
|
spec.bindir = 'bin'
|
data/lib/diffcrypt.rb
CHANGED
data/lib/diffcrypt/cli.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative './encryptor'
|
4
|
+
require_relative './file'
|
4
5
|
require_relative './version'
|
5
6
|
|
6
7
|
module Diffcrypt
|
@@ -8,17 +9,24 @@ module Diffcrypt
|
|
8
9
|
desc 'decrypt <path>', 'Decrypt a file'
|
9
10
|
method_option :key, aliases: %i[k], required: true
|
10
11
|
def decrypt(path)
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
file = File.new(path)
|
13
|
+
ensure_file_exists(file)
|
14
|
+
say file.decrypt(key)
|
14
15
|
end
|
15
16
|
|
16
17
|
desc 'encrypt <path>', 'Encrypt a file'
|
17
18
|
method_option :key, aliases: %i[k], required: true
|
19
|
+
method_option :cipher, default: Encryptor::DEFAULT_CIPHER
|
18
20
|
def encrypt(path)
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
file = File.new(path)
|
22
|
+
ensure_file_exists(file)
|
23
|
+
say file.encrypt(key, cipher: options[:cipher])
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'generate-key', 'Generate a 32 bit key'
|
27
|
+
method_option :cipher, default: Encryptor::DEFAULT_CIPHER
|
28
|
+
def generate_key
|
29
|
+
say Encryptor.generate_key(options[:cipher])
|
22
30
|
end
|
23
31
|
|
24
32
|
desc 'version', 'Show client version'
|
@@ -35,8 +43,9 @@ module Diffcrypt
|
|
35
43
|
@encryptor ||= Encryptor.new(key)
|
36
44
|
end
|
37
45
|
|
38
|
-
|
39
|
-
|
46
|
+
# @param [Diffcrypt::File] path
|
47
|
+
def ensure_file_exists(file)
|
48
|
+
abort('[ERROR] File does not exist') unless file.exists?
|
40
49
|
end
|
41
50
|
|
42
51
|
def self.exit_on_failure?
|
data/lib/diffcrypt/encryptor.rb
CHANGED
@@ -12,15 +12,16 @@ require_relative './version'
|
|
12
12
|
|
13
13
|
module Diffcrypt
|
14
14
|
class Encryptor
|
15
|
-
|
15
|
+
DEFAULT_CIPHER = 'aes-256-gcm'
|
16
16
|
|
17
|
-
def self.generate_key
|
18
|
-
SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(
|
17
|
+
def self.generate_key(cipher = DEFAULT_CIPHER)
|
18
|
+
SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(cipher))
|
19
19
|
end
|
20
20
|
|
21
|
-
def initialize(key)
|
21
|
+
def initialize(key, cipher: DEFAULT_CIPHER)
|
22
22
|
@key = key
|
23
|
-
@
|
23
|
+
@cipher = cipher
|
24
|
+
@encryptor ||= ActiveSupport::MessageEncryptor.new([key].pack('H*'), cipher: cipher)
|
24
25
|
end
|
25
26
|
|
26
27
|
# @param [String] contents The raw YAML string to be encrypted
|
@@ -46,12 +47,11 @@ module Diffcrypt
|
|
46
47
|
# @param [String] contents The raw YAML string to be encrypted
|
47
48
|
# @param [String, nil] original_encrypted_contents The original (encrypted) content to determine which keys have changed
|
48
49
|
# @return [String]
|
49
|
-
def encrypt(contents, original_encrypted_contents = nil)
|
50
|
+
def encrypt(contents, original_encrypted_contents = nil, cipher: nil)
|
50
51
|
data = encrypt_data contents, original_encrypted_contents
|
51
52
|
YAML.dump(
|
52
53
|
'client' => "diffcrypt-#{Diffcrypt::VERSION}",
|
53
|
-
'cipher' =>
|
54
|
-
'checksum' => Digest::MD5.hexdigest(Marshal.dump(data)),
|
54
|
+
'cipher' => cipher || @cipher,
|
55
55
|
'data' => data,
|
56
56
|
)
|
57
57
|
end
|
@@ -86,7 +86,7 @@ module Diffcrypt
|
|
86
86
|
key_changed ? encrypt_string(value) : original_encrypted_value
|
87
87
|
end
|
88
88
|
end
|
89
|
-
data
|
89
|
+
data.sort.to_h
|
90
90
|
end
|
91
91
|
# rubocop:enable Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity
|
92
92
|
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './encryptor'
|
4
|
+
|
5
|
+
module Diffcrypt
|
6
|
+
class File
|
7
|
+
attr_reader :file
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
@path = ::File.absolute_path path
|
11
|
+
end
|
12
|
+
|
13
|
+
def encrypted?
|
14
|
+
to_yaml['cipher']
|
15
|
+
end
|
16
|
+
|
17
|
+
# Determines the cipher to use for encryption/decryption
|
18
|
+
def cipher
|
19
|
+
return 'aes-128-gcm' if format == 'activesupport'
|
20
|
+
|
21
|
+
to_yaml['cipher'] || Encryptor::DEFAULT_CIPHER
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Boolean]
|
25
|
+
def exists?
|
26
|
+
::File.exist?(@path)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Determines the format to be used for encryption
|
30
|
+
# @return [String] diffcrypt|activesupport
|
31
|
+
def format
|
32
|
+
return 'diffcrypt' if read == ''
|
33
|
+
return 'diffcrypt' if read.index('---')&.zero?
|
34
|
+
|
35
|
+
'activesupport'
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String] Raw contents of the file
|
39
|
+
def read
|
40
|
+
return '' unless ::File.exist?(@path)
|
41
|
+
|
42
|
+
@read ||= ::File.read(@path)
|
43
|
+
@read
|
44
|
+
end
|
45
|
+
|
46
|
+
# Save the encrypted contents back to disk
|
47
|
+
# @return [Boolean] True is file save was successful
|
48
|
+
def write(key, data, cipher: nil)
|
49
|
+
cipher ||= self.cipher
|
50
|
+
yaml = ::YAML.dump(data)
|
51
|
+
contents = Encryptor.new(key, cipher: cipher).encrypt(yaml)
|
52
|
+
::File.write(@path, contents)
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO: This seems useless, figure out what's up
|
56
|
+
def encrypt(key, cipher: DEFAULT_CIPHER)
|
57
|
+
return read if encrypted?
|
58
|
+
|
59
|
+
Encryptor.new(key, cipher: cipher).encrypt(read)
|
60
|
+
end
|
61
|
+
|
62
|
+
# TODO: Add a test to verify this does descrypt properly
|
63
|
+
def decrypt(key)
|
64
|
+
return read unless encrypted?
|
65
|
+
|
66
|
+
Encryptor.new(key, cipher: cipher).decrypt(read)
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_yaml
|
70
|
+
@to_yaml ||= YAML.safe_load(read) || {}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './encrypted_configuration'
|
4
|
+
|
5
|
+
module Diffcrypt
|
6
|
+
module Rails
|
7
|
+
module ApplicationHelper
|
8
|
+
def encrypted(path, key_path: 'config/master.key', env_key: 'RAILS_MASTER_KEY')
|
9
|
+
config_path, key_path = resolve_encrypted_paths(path, key_path)
|
10
|
+
|
11
|
+
Diffcrypt::Rails::EncryptedConfiguration.new(
|
12
|
+
config_path: config_path,
|
13
|
+
key_path: key_path,
|
14
|
+
env_key: env_key,
|
15
|
+
raise_if_missing_key: config.require_master_key,
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def resolve_encrypted_paths(config_path, key_path)
|
22
|
+
config_path_abs = ::Rails.root.join(config_path)
|
23
|
+
key_path_abs = ::Rails.root.join(key_path)
|
24
|
+
|
25
|
+
# We always want to use `config/credentials/[environment]` for consistency
|
26
|
+
# If the master credentials do not exist, and a user has not specificed an environment, default to development
|
27
|
+
if config_path == 'config/credentials.yml.enc' && ::File.exist?(config_path_abs.to_s) == false
|
28
|
+
config_path_abs = ::Rails.root.join('config/credentials/development.yml.enc')
|
29
|
+
key_path_abs = ::Rails.root.join('config/credentials/development.key')
|
30
|
+
end
|
31
|
+
|
32
|
+
[
|
33
|
+
config_path_abs,
|
34
|
+
key_path_abs,
|
35
|
+
]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'fileutils'
|
3
4
|
require 'pathname'
|
4
5
|
require 'tmpdir'
|
5
6
|
|
@@ -8,6 +9,8 @@ require 'active_support/core_ext/hash'
|
|
8
9
|
require 'active_support/core_ext/module/delegation'
|
9
10
|
require 'active_support/core_ext/object/inclusion'
|
10
11
|
|
12
|
+
require 'diffcrypt/file'
|
13
|
+
|
11
14
|
module Diffcrypt
|
12
15
|
module Rails
|
13
16
|
class EncryptedConfiguration
|
@@ -20,13 +23,13 @@ module Diffcrypt
|
|
20
23
|
delegate_missing_to :options
|
21
24
|
|
22
25
|
def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
|
23
|
-
@content_path = Pathname.new(File.absolute_path(config_path)).yield_self do |path|
|
26
|
+
@content_path = Pathname.new(::File.absolute_path(config_path)).yield_self do |path|
|
24
27
|
path.symlink? ? path.realpath : path
|
25
28
|
end
|
29
|
+
@diffcrypt_file = Diffcrypt::File.new(@content_path)
|
26
30
|
@key_path = Pathname.new(key_path)
|
27
31
|
@env_key = env_key
|
28
32
|
@raise_if_missing_key = raise_if_missing_key
|
29
|
-
@active_support_encryptor = ActiveSupport::MessageEncryptor.new([key].pack('H*'), cipher: Encryptor::CIPHER)
|
30
33
|
end
|
31
34
|
|
32
35
|
# Determines if file is using the diffable format, or still
|
@@ -50,7 +53,7 @@ module Diffcrypt
|
|
50
53
|
deserialize(contents)
|
51
54
|
|
52
55
|
IO.binwrite "#{content_path}.tmp", encrypt(contents, original_encrypted_contents)
|
53
|
-
FileUtils.mv "#{content_path}.tmp", content_path
|
56
|
+
::FileUtils.mv "#{content_path}.tmp", content_path
|
54
57
|
end
|
55
58
|
|
56
59
|
def config
|
@@ -72,7 +75,7 @@ module Diffcrypt
|
|
72
75
|
# rubocop:disable Metrics/AbcSize
|
73
76
|
def writing(contents)
|
74
77
|
tmp_file = "#{Process.pid}.#{content_path.basename.to_s.chomp('.enc')}"
|
75
|
-
tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file)
|
78
|
+
tmp_path = Pathname.new ::File.join(Dir.tmpdir, tmp_file)
|
76
79
|
tmp_path.binwrite contents
|
77
80
|
|
78
81
|
yield tmp_path
|
@@ -81,10 +84,17 @@ module Diffcrypt
|
|
81
84
|
|
82
85
|
write(updated_contents, content_path_diffable? && content_path.binread)
|
83
86
|
ensure
|
84
|
-
FileUtils.rm(tmp_path) if tmp_path&.exist?
|
87
|
+
::FileUtils.rm(tmp_path) if tmp_path&.exist?
|
85
88
|
end
|
86
89
|
# rubocop:enable Metrics/AbcSize
|
87
90
|
|
91
|
+
# Standard rails credentials encrypt the entire file. We need to detect this to use the correct
|
92
|
+
# data interface
|
93
|
+
# @return [Boolean]
|
94
|
+
def rails_native_credentials?(contents)
|
95
|
+
contents.index('---').nil?
|
96
|
+
end
|
97
|
+
|
88
98
|
# @param [String] contents The new content to be encrypted
|
89
99
|
# @param [String] diff_against The original (encrypted) content to determine which keys have changed
|
90
100
|
# @return [String] Encrypted content to commit
|
@@ -95,16 +105,25 @@ module Diffcrypt
|
|
95
105
|
# @param [String] contents
|
96
106
|
# @return [String]
|
97
107
|
def decrypt(contents)
|
98
|
-
if contents
|
99
|
-
|
108
|
+
if rails_native_credentials?(contents)
|
109
|
+
active_support_encryptor.decrypt_and_verify contents
|
100
110
|
else
|
101
111
|
encryptor.decrypt contents
|
102
112
|
end
|
103
113
|
end
|
104
114
|
|
115
|
+
# Rails applications with an existing credentials file, the inbuilt active support encryptor should be used
|
116
|
+
# @return [ActiveSupport::MessageEncryptor]
|
117
|
+
def active_support_encryptor
|
118
|
+
@active_support_encryptor ||= ActiveSupport::MessageEncryptor.new(
|
119
|
+
[key].pack('H*'),
|
120
|
+
cipher: 'aes-128-gcm',
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
105
124
|
# @return [Encryptor]
|
106
125
|
def encryptor
|
107
|
-
@encryptor ||= Encryptor.new key
|
126
|
+
@encryptor ||= Encryptor.new key, cipher: @diffcrypt_file.cipher
|
108
127
|
end
|
109
128
|
|
110
129
|
def read_env_key
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'diffcrypt/rails/application_helper'
|
4
|
+
|
5
|
+
module Diffcrypt
|
6
|
+
class Railtie < ::Rails::Railtie
|
7
|
+
railtie_name :diffcrypt
|
8
|
+
|
9
|
+
rake_tasks do
|
10
|
+
path = ::File.expand_path(__dir__)
|
11
|
+
::Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :diffcrypt do
|
4
|
+
desc 'Initialize credentials for all environments'
|
5
|
+
task :init, %i[environments] do |_t, args|
|
6
|
+
args.with_defaults(
|
7
|
+
environments: 'development,test,staging,production',
|
8
|
+
)
|
9
|
+
environments = args.environments.split(',')
|
10
|
+
|
11
|
+
environments.each do |environment|
|
12
|
+
key_path = Rails.root.join('config', 'credentials', "#{environment}.key")
|
13
|
+
file_path = Rails.root.join('config', 'credentials', "#{environment}.yml.enc")
|
14
|
+
gitignore_path = Rails.root.join('.gitignore')
|
15
|
+
next if File.exist?(file_path) || File.exist?(key_path)
|
16
|
+
|
17
|
+
# Generate a new key
|
18
|
+
key = Diffcrypt::Encryptor.generate_key
|
19
|
+
key_dir = File.dirname(key_path)
|
20
|
+
Dir.mkdir(key_dir) unless Dir.exist?(key_dir)
|
21
|
+
::File.write(key_path, key)
|
22
|
+
|
23
|
+
# Encrypt default contents
|
24
|
+
file = Diffcrypt::File.new(file_path)
|
25
|
+
data = {
|
26
|
+
'secret_key_base' => SecureRandom.hex(32),
|
27
|
+
}
|
28
|
+
file.write(key, data)
|
29
|
+
|
30
|
+
# Ensure .key files are always ignored
|
31
|
+
::File.open(gitignore_path, 'a') do |f|
|
32
|
+
f.write("\nconfig/credentials/*.key")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/diffcrypt/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diffcrypt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc Qualie
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- LICENSE.txt
|
63
63
|
- README.md
|
64
64
|
- Rakefile
|
65
|
+
- SECURITY.md
|
65
66
|
- bin/console
|
66
67
|
- bin/diffcrypt
|
67
68
|
- bin/setup
|
@@ -69,8 +70,13 @@ files:
|
|
69
70
|
- lib/diffcrypt.rb
|
70
71
|
- lib/diffcrypt/cli.rb
|
71
72
|
- lib/diffcrypt/encryptor.rb
|
73
|
+
- lib/diffcrypt/file.rb
|
74
|
+
- lib/diffcrypt/rails/application_helper.rb
|
72
75
|
- lib/diffcrypt/rails/encrypted_configuration.rb
|
76
|
+
- lib/diffcrypt/railtie.rb
|
77
|
+
- lib/diffcrypt/tasks/rails.rake
|
73
78
|
- lib/diffcrypt/version.rb
|
79
|
+
- tmp/.keep
|
74
80
|
homepage: https://github.com/marcqualie/diffcrypt
|
75
81
|
licenses:
|
76
82
|
- MIT
|
@@ -85,7 +91,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
91
|
requirements:
|
86
92
|
- - ">="
|
87
93
|
- !ruby/object:Gem::Version
|
88
|
-
version: 2.
|
94
|
+
version: 2.6.0
|
89
95
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
96
|
requirements:
|
91
97
|
- - ">="
|