diffcrypt 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: edd41bfcd4f86767e1d8b98d4c161c38983962e273a6e5b486c0f6465f159d80
4
- data.tar.gz: fecb2bda4f1f7cbd53fea2fd333f723bd3c1ac9e8c206930c6783795a6260d10
3
+ metadata.gz: e40a357c62cf93d9c49f336c7ad456d4f7510bd8389f3fb20c4913b91f4ef5f2
4
+ data.tar.gz: 4fb0296c2eaf901c59bdec117f974203500596f6347a9b8c12632b7dbe39ab77
5
5
  SHA512:
6
- metadata.gz: 7fe1aedc3eec0f98370479a8a813e15a9458213e70d49345ae90234cead04e47b880a602af54ff8d43a1a6e4d54e7bef4a2955eb3f88fde68762bb08de16a555
7
- data.tar.gz: 6a020043e24b13d22414de6f810bde69bb578adc0041d34e33c8f3d220d68903ed8b3726bc7302138fbdc00ebfe8c2e7a59bca4e776519a3d898fc4cd4d51845
6
+ metadata.gz: d368c674a915b706b876a5a022c7003e3f7ecf30b38b581f2e7debf261d47102d759c27d317b85a71904bace22fd1a0306d9175b2c9496592375d786d3fe4fc1
7
+ data.tar.gz: 9e24c98075aab9c2f8e0550e757adea3df5b7f1e341f7d93d5644ee21e6a9ee9a352f7450a1a63afd609d876406c6efbe4aaff56e6c978c413259588625eedf5
@@ -4,8 +4,23 @@ jobs:
4
4
  build:
5
5
  docker:
6
6
  - image: circleci/ruby:2.6.6
7
+ working_directory: /mnt/ramdisk
7
8
  steps:
8
9
  - checkout
9
10
  - run: bundle install
10
- - run: bundle exec rake test
11
- - run: bundle exec rubocop
11
+ - run:
12
+ name: Setup Code Climate test-reporter
13
+ command: |
14
+ # download test reporter as a static binary
15
+ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
16
+ chmod +x ./cc-test-reporter
17
+ - run:
18
+ name: rake test
19
+ command: |
20
+ ./cc-test-reporter before-build
21
+ bundle exec rake test
22
+ ./cc-test-reporter after-build --coverage-input-type lcov --exit-code $?
23
+ - run:
24
+ name: rubocop
25
+ command: bundle exec rubocop
26
+ when: always
@@ -9,8 +9,12 @@ Style/Documentation:
9
9
  Metrics/MethodLength:
10
10
  Exclude:
11
11
  - test/**/*_test.rb
12
+ TrailingCommaInArrayLiteral:
13
+ EnforcedStyleForMultiline: consistent_comma
12
14
  Style/TrailingCommaInArguments:
13
15
  EnforcedStyleForMultiline: consistent_comma
16
+ Style/AccessorGrouping:
17
+ EnforcedStyle: separated
14
18
 
15
19
  Layout/LineLength:
16
20
  Exclude:
@@ -0,0 +1,95 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+
9
+
10
+ ## [0.4.0] - 2020-10-01
11
+
12
+ ### Changed
13
+
14
+ - Encryptor can now use other ciphers than the default
15
+
16
+ ### Dependencies
17
+
18
+ - simplecov: 0.17.0 -> 0.18.0 (#20)
19
+ - rubocop: 0.88.0 -> 0.92.0 (#24)
20
+
21
+
22
+
23
+ ## [0.3.3] - 2020-07-25
24
+
25
+ ### Fixed
26
+
27
+ - Explicit FileUtils require to avoid potentially warning logs
28
+
29
+
30
+
31
+ ## [0.3.2] - 2020-07-20
32
+
33
+ ### Added
34
+
35
+ - CLI: `diffcrypt generate-key` command to generate a new key for a cipher
36
+ - Internal: Library now generates and publishes code coverage publically on Code Climate
37
+
38
+ ### Changed
39
+
40
+ - Only support ruby 2.5+ since 2.4 is no longer maintained
41
+
42
+ ### Removed
43
+
44
+ - No longer generate and store a checksum. Backwards compatible since it wasn't used
45
+
46
+
47
+
48
+ ## [0.3.1] - 2020-07-08
49
+
50
+ ### Fixed
51
+
52
+ - Thor deprecation error no longer shows on CLI failure
53
+
54
+ ### Changed
55
+
56
+ - Thor 0.20+ can now be used alongside this gem
57
+
58
+
59
+
60
+ ## [0.3.0] - 2020-06-30
61
+
62
+ ## Added
63
+
64
+ - CLI: Use diffcrypt from command line of any project without requiring ruby integration
65
+ - CLI: `diffcrypt encrypt` Directly encrypt any file and output the contents
66
+ - CLI: `diffcrypt decrypt` Directly decrypt any file and output the contents
67
+
68
+
69
+
70
+ ## [0.2.0] - 2020-06-28
71
+
72
+ ### Added
73
+
74
+ - Store client, cipher and checksum in file metadata
75
+
76
+ ### Fixed
77
+
78
+ - Only attenpt to decrypt original content if it exists
79
+
80
+
81
+
82
+ ## [0.1.1] - 2020-06-28
83
+
84
+ ### Fixed
85
+
86
+ - Converting rails native credentials files would fail on first run
87
+
88
+
89
+
90
+ ## [0.1.0] - 2020-06-28
91
+
92
+ ### Added
93
+
94
+ - First release!
95
+ - Rails support via monkey patch
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', '~> 0.86'
10
+ gem 'rubocop', '~> 0.92.0'
11
+ gem 'simplecov', '~> 0.19.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
@@ -1,5 +1,9 @@
1
1
  # Diffcrypt
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/diffcrypt.svg)](https://rubygems.org/gems/diffcrypt)
4
+ [![CircleCI](https://circleci.com/gh/marcqualie/diffcrypt.svg?style=svg)](https://circleci.com/gh/marcqualie/diffcrypt)
5
+
6
+
3
7
  Diffable encrypted files that you can safely commit into your repo.
4
8
 
5
9
 
@@ -16,7 +20,7 @@ And then execute:
16
20
 
17
21
  $ bundle install
18
22
 
19
- Or install it yourself as:
23
+ Or install it globally (to use the CLI from any project):
20
24
 
21
25
  $ gem install diffcrypt
22
26
 
@@ -24,8 +28,24 @@ Or install it yourself as:
24
28
 
25
29
  ## Usage
26
30
 
31
+ There are a few ways to use the library, depending on how advanced your use case is.
32
+
33
+
34
+ ### CLI
35
+
36
+ The easiest way to get started is to use the CLI.
37
+
38
+ ```shell
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
+ ```
42
+
43
+
44
+ ### Ruby
27
45
 
28
- ### Encrypt existing file
46
+ A direct API is exposed so `Diffcrypt::Encryptor` can be used in any ruby project.
47
+
48
+ **NOTE:** This API may change any time until v1.0
29
49
 
30
50
  ```ruby
31
51
  encryptor = Diffcrypt::Encryptor.new('99e1f86b9e61f24c56ff4108dd415091')
@@ -34,24 +54,22 @@ encrypted = encryptor.encrypt(yaml)
34
54
  File.write('tmp/example.yml.enc', encrypted)
35
55
  ```
36
56
 
37
- ### Decrypt a file
38
-
39
57
  ```ruby
40
58
  encryptor = Diffcrypt::Encryptor.new('99e1f86b9e61f24c56ff4108dd415091')
41
59
  yaml = File.read('test/fixtures/example.yml.enc')
42
60
  config = YAML.safe_load(encryptor.decrypt(yaml))
43
61
  ```
44
62
 
45
- ### Rails
63
+ ### Ruby on Rails
46
64
 
47
65
  Currently there is not native support for rails, but ActiveSupport can be monkeypatched to override
48
- the built in encrypter.
66
+ the built in encrypter. All existing `rails credentials:edit` also work with this method.
49
67
 
50
68
  ```ruby
51
69
  require 'diffcrypt/rails/encrypted_configuration'
52
70
  module Rails
53
71
  class Application
54
- def encrypted(path, key_path: 'config/master.key', env_key: 'RAILS_MASTER_KEY')
72
+ def encrypted(path, key_path: 'config/aes-128-gcm.key', env_key: 'RAILS_MASTER_KEY')
55
73
  Diffcrypt::Rails::EncryptedConfiguration.new(
56
74
  config_path: Rails.root.join(path),
57
75
  key_path: Rails.root.join(key_path),
@@ -65,6 +83,17 @@ end
65
83
 
66
84
 
67
85
 
86
+ ## Converting between ciphers
87
+
88
+ Sometimes you may want to rotate the cipher used on a file. You cab do this rogramtically using the ruby code above, or you can also chain the CLI commands like so:
89
+
90
+ ```shell
91
+ diffcrypt decrypt -k $(cat test/fixtures/aes-128-gcm.key) test/fixtures/example.yml.enc > test/fixtures/example.128.yml \
92
+ && 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
93
+ ```
94
+
95
+
96
+
68
97
  ## Development
69
98
 
70
99
  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.
@@ -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.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'thor'
5
+
6
+ require_relative '../lib/diffcrypt/cli'
7
+
8
+ Diffcrypt::CLI.start(ARGV)
@@ -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.3.0')
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
16
16
 
17
17
  # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
18
 
@@ -22,12 +22,13 @@ 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
- spec.bindir = 'exe'
29
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.bindir = 'bin'
29
+ spec.executables = %w[diffcrypt]
30
30
  spec.require_paths = ['lib']
31
31
 
32
32
  spec.add_runtime_dependency 'activesupport', '~> 6.0.0'
33
+ spec.add_runtime_dependency 'thor', '>= 0.20', '< 2'
33
34
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './encryptor'
4
+ require_relative './file'
5
+ require_relative './version'
6
+
7
+ module Diffcrypt
8
+ class CLI < Thor
9
+ desc 'decrypt <path>', 'Decrypt a file'
10
+ method_option :key, aliases: %i[k], required: true
11
+ def decrypt(path)
12
+ file = File.new(path)
13
+ ensure_file_exists(file)
14
+ say file.decrypt(key)
15
+ end
16
+
17
+ desc 'encrypt <path>', 'Encrypt a file'
18
+ method_option :key, aliases: %i[k], required: true
19
+ method_option :cipher, default: Encryptor::DEFAULT_CIPHER
20
+ def encrypt(path)
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])
30
+ end
31
+
32
+ desc 'version', 'Show client version'
33
+ def version
34
+ say Diffcrypt::VERSION
35
+ end
36
+
37
+ no_commands do
38
+ def key
39
+ options[:key]
40
+ end
41
+
42
+ def encryptor
43
+ @encryptor ||= Encryptor.new(key)
44
+ end
45
+
46
+ # @param [Diffcrypt::File] path
47
+ def ensure_file_exists(file)
48
+ abort('[ERROR] File does not exist') unless file.exists?
49
+ end
50
+
51
+ def self.exit_on_failure?
52
+ true
53
+ end
54
+ end
55
+ end
56
+ end
@@ -8,17 +8,20 @@ require 'yaml'
8
8
 
9
9
  require 'active_support/message_encryptor'
10
10
 
11
+ require_relative './version'
12
+
11
13
  module Diffcrypt
12
14
  class Encryptor
13
- CIPHER = 'aes-128-gcm'
15
+ DEFAULT_CIPHER = 'aes-128-gcm'
14
16
 
15
- def self.generate_key
16
- SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
17
+ def self.generate_key(cipher = DEFAULT_CIPHER)
18
+ SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(cipher))
17
19
  end
18
20
 
19
- def initialize(key)
21
+ def initialize(key, cipher: DEFAULT_CIPHER)
20
22
  @key = key
21
- @encryptor ||= ActiveSupport::MessageEncryptor.new([key].pack('H*'), cipher: CIPHER)
23
+ @cipher = cipher
24
+ @encryptor ||= ActiveSupport::MessageEncryptor.new([key].pack('H*'), cipher: cipher)
22
25
  end
23
26
 
24
27
  # @param [String] contents The raw YAML string to be encrypted
@@ -48,8 +51,7 @@ module Diffcrypt
48
51
  data = encrypt_data contents, original_encrypted_contents
49
52
  YAML.dump(
50
53
  'client' => "diffcrypt-#{Diffcrypt::VERSION}",
51
- 'cipher' => CIPHER,
52
- 'checksum' => Digest::MD5.hexdigest(Marshal.dump(data)),
54
+ 'cipher' => @cipher,
53
55
  'data' => data,
54
56
  )
55
57
  end
@@ -0,0 +1,48 @@
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
+ def cipher
18
+ to_yaml['cipher'] || Encryptor::DEFAULT_CIPHER
19
+ end
20
+
21
+ # @return [Boolean]
22
+ def exists?
23
+ ::File.exist?(@path)
24
+ end
25
+
26
+ # @return [String] Raw contents of the file
27
+ def read
28
+ @read ||= ::File.read(@path)
29
+ end
30
+
31
+ def encrypt(key, cipher: DEFAULT_CIPHER)
32
+ return read if encrypted?
33
+
34
+ Encryptor.new(key, cipher: cipher).encrypt(read)
35
+ end
36
+
37
+ # TODO: Add a test to verify this does descrypt properly
38
+ def decrypt(key)
39
+ return read unless encrypted?
40
+
41
+ Encryptor.new(key, cipher: cipher).decrypt(read)
42
+ end
43
+
44
+ def to_yaml
45
+ @to_yaml ||= YAML.safe_load(read)
46
+ end
47
+ end
48
+ 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,7 +84,7 @@ 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
 
@@ -102,9 +105,18 @@ module Diffcrypt
102
105
  end
103
106
  end
104
107
 
108
+ # Rails applications with an existing credentials file, the inbuilt active support encryptor should be used
109
+ # @return [ActiveSupport::MessageEncryptor]
110
+ def active_support_encryptor
111
+ @active_support_encryptor = ActiveSupport::MessageEncryptor.new(
112
+ [key].pack('H*'),
113
+ cipher: @diffcrypt_file.cipher,
114
+ )
115
+ end
116
+
105
117
  # @return [Encryptor]
106
118
  def encryptor
107
- @encryptor ||= Encryptor.new key
119
+ @encryptor ||= Encryptor.new key, cipher: @diffcrypt_file.cipher
108
120
  end
109
121
 
110
122
  def read_env_key
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Diffcrypt
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.0'
5
5
  end
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.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc Qualie
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-28 00:00:00.000000000 Z
11
+ date: 2020-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,11 +24,32 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 6.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.20'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '2'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0.20'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '2'
27
47
  description: Diffable encrypted configuration files that can be safely committed into
28
48
  a git repository
29
49
  email:
30
50
  - marc@marcqualie.com
31
- executables: []
51
+ executables:
52
+ - diffcrypt
32
53
  extensions: []
33
54
  extra_rdoc_files: []
34
55
  files:
@@ -36,17 +57,23 @@ files:
36
57
  - ".github/dependabot.yml"
37
58
  - ".gitignore"
38
59
  - ".rubocop.yml"
60
+ - CHANGELOG.md
39
61
  - Gemfile
40
62
  - LICENSE.txt
41
63
  - README.md
42
64
  - Rakefile
65
+ - SECURITY.md
43
66
  - bin/console
67
+ - bin/diffcrypt
44
68
  - bin/setup
45
69
  - diffcrypt.gemspec
46
70
  - lib/diffcrypt.rb
71
+ - lib/diffcrypt/cli.rb
47
72
  - lib/diffcrypt/encryptor.rb
73
+ - lib/diffcrypt/file.rb
48
74
  - lib/diffcrypt/rails/encrypted_configuration.rb
49
75
  - lib/diffcrypt/version.rb
76
+ - tmp/.keep
50
77
  homepage: https://github.com/marcqualie/diffcrypt
51
78
  licenses:
52
79
  - MIT
@@ -61,7 +88,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
88
  requirements:
62
89
  - - ">="
63
90
  - !ruby/object:Gem::Version
64
- version: 2.3.0
91
+ version: 2.5.0
65
92
  required_rubygems_version: !ruby/object:Gem::Requirement
66
93
  requirements:
67
94
  - - ">="