legion-crypt 0.2.0 → 1.2.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.
data/NOTICE.txt ADDED
@@ -0,0 +1,9 @@
1
+ <Insert Project Name Here>
2
+ Copyright 2020 Optum
3
+
4
+ Project Description:
5
+ ====================
6
+ <Short description of your project>
7
+
8
+ Author(s):
9
+ <List the names and GitHub IDs of the original project authors>
data/README.md CHANGED
@@ -1,44 +1,56 @@
1
- # Legion::Crypt
1
+ Legion::Crypt
2
+ =====
2
3
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/legion/crypt`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+ Legion::Crypt is the class responsible for encryption, managing secrets and connecting with Vault
4
5
 
5
- TODO: Delete this and the text above, and describe your gem
6
+ Supported Ruby versions and implementations
7
+ ------------------------------------------------
6
8
 
7
- ## Installation
9
+ Legion::Crypt should work identically on:
8
10
 
9
- Add this line to your application's Gemfile:
11
+ * JRuby 9.2+
12
+ * Ruby 2.4+
10
13
 
11
- ```ruby
12
- gem 'legion-crypt'
13
- ```
14
-
15
- And then execute:
16
-
17
- $ bundle install
18
-
19
- Or install it yourself as:
20
-
21
- $ gem install legion-crypt
22
14
 
23
- ## Usage
15
+ Installation and Usage
16
+ ------------------------
24
17
 
25
- Ciper class
26
- 1) check to see if connected to vault, if so, use that
27
- 2) check to see if it was set via config
28
- 3) request it from the cluster
29
- 4) generate it
18
+ You can verify your installation using this piece of code:
30
19
 
31
- ## Development
32
-
33
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
34
-
35
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
20
+ ```bash
21
+ gem install legion-crypt
22
+ ```
36
23
 
37
- ## Contributing
24
+ ```ruby
25
+ require 'legion/crypt'
38
26
 
39
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/legion-crypt.
27
+ Legion::Crypt.start
28
+ Legion::Crypt.encrypt('this is my string')
29
+ Legion::Crypt.decrypt(message)
30
+ ```
40
31
 
32
+ Settings
33
+ ----------
34
+
35
+ ```json
36
+ {
37
+ "vault": {
38
+ "enabled": false,
39
+ "protocol": "http",
40
+ "address": "localhost",
41
+ "port": 8200,
42
+ "token": null,
43
+ "connected": false
44
+ },
45
+ "cs_encrypt_ready": false,
46
+ "dynamic_keys": true,
47
+ "cluster_secret": null,
48
+ "save_private_key": false,
49
+ "read_private_key": false
50
+ }
51
+ ```
41
52
 
42
- ## License
53
+ Authors
54
+ ----------
43
55
 
44
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
56
+ * [Matthew Iverson](https://github.com/Esity) - current maintainer
data/SECURITY.md ADDED
@@ -0,0 +1,9 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+ | Version | Supported |
5
+ | ------- | ------------------ |
6
+ | 1.x.x | :white_check_mark: |
7
+
8
+ ## Reporting a Vulnerability
9
+ To be added
data/attribution.txt ADDED
@@ -0,0 +1 @@
1
+ Add attributions here.
data/legion-crypt.gemspec CHANGED
@@ -3,37 +3,30 @@
3
3
  require_relative 'lib/legion/crypt/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = 'legion-crypt'
6
+ spec.name = 'legion-crypt'
7
7
  spec.version = Legion::Crypt::VERSION
8
8
  spec.authors = ['Esity']
9
- spec.email = ['matthewdiverson@gmail.com']
10
-
11
- spec.summary = 'Legion::Vault is used to keep things safe'
12
- spec.description = 'Integrates with Hashicorps vault and other encryption type things'
13
- spec.homepage = 'https://bitbucket.org/legion-io/legion-vault/'
14
- spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
16
-
17
- spec.metadata['homepage_uri'] = spec.homepage
18
- spec.metadata['source_code_uri'] = 'https://bitbucket.org/legion-io/legion/'
19
- spec.metadata['changelog_uri'] = 'https://bitbucket.org/legion-io/legion/src/master/CHANGELOG.md'
20
-
21
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
- end
24
- spec.bindir = 'exe'
25
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
9
+ spec.email = %w[matthewdiverson@gmail.com ruby@optum.com]
10
+ spec.summary = 'Handles requests for encrypt, decrypting, connecting to Vault, among other things'
11
+ spec.description = 'A gem used by the LegionIO framework for encryption'
12
+ spec.homepage = 'https://github.com/Optum/legion-crypt'
13
+ spec.license = 'Apache-2.0'
26
14
  spec.require_paths = ['lib']
15
+ spec.required_ruby_version = '>= 2.4'
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.test_files = spec.files.select { |p| p =~ %r{^test/.*_test.rb} }
18
+ spec.extra_rdoc_files = %w[README.md LICENSE CHANGELOG.md]
19
+ spec.metadata = {
20
+ 'bug_tracker_uri' => 'https://github.com/Optum/legion-crypt/issues',
21
+ 'changelog_uri' => 'https://github.com/Optum/legion-crypt/src/main/CHANGELOG.md',
22
+ 'documentation_uri' => 'https://github.com/Optum/legion-crypt',
23
+ 'homepage_uri' => 'https://github.com/Optum/LegionIO',
24
+ 'source_code_uri' => 'https://github.com/Optum/legion-crypt',
25
+ 'wiki_uri' => 'https://github.com/Optum/legion-crypt/wiki'
26
+ }
27
27
 
28
- spec.add_dependency 'rbnacl'
29
- spec.add_dependency 'vault'
28
+ spec.add_dependency 'vault', '>= 0.15.0'
30
29
 
31
30
  spec.add_development_dependency 'legion-logging'
32
31
  spec.add_development_dependency 'legion-settings'
33
- spec.add_development_dependency 'legion-transport'
34
- # spec.add_development_dependency 'legionio'
35
- spec.add_development_dependency 'rake'
36
- spec.add_development_dependency 'rspec'
37
- spec.add_development_dependency 'rubocop'
38
- # spec.add_development_dependency 'simplecov', '< 0.18.0'
39
32
  end
data/lib/legion/crypt.rb CHANGED
@@ -1,14 +1,8 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'openssl'
2
+ require 'base64'
4
3
  require 'legion/crypt/version'
5
4
  require 'legion/crypt/settings'
6
- require 'rbnacl'
7
- require 'base64'
8
-
9
- require 'legion/crypt/box'
10
5
  require 'legion/crypt/cipher'
11
- require 'legion/crypt/vault'
12
6
 
13
7
  module Legion
14
8
  module Crypt
@@ -16,13 +10,25 @@ module Legion
16
10
  attr_reader :sessions
17
11
 
18
12
  include Legion::Crypt::Cipher
19
- include Legion::Crypt::Vault if Legion::Settings[:crypt][:vault][:enabled]
13
+
14
+ unless Gem::Specification.find_by_name('vault').nil?
15
+ require 'legion/crypt/vault'
16
+ include Legion::Crypt::Vault
17
+ end
20
18
 
21
19
  def start
22
20
  Legion::Logging.debug 'Legion::Crypt is running start'
23
- # load_keys if Dir.exist?('./settings') && File.exist?('./settings/private.key') && File.exist?('./settings/public.key')
21
+ ::File.write('./legionio.key', private_key) if settings[:save_private_key]
22
+
23
+ connect_vault unless settings[:vault][:token].nil?
24
+ end
24
25
 
25
- # connect_vault unless Legion::Settings[:crypt][:vault][:token].nil?
26
+ def settings
27
+ if Legion.const_defined?('Settings')
28
+ Legion::Settings[:crypt]
29
+ else
30
+ Legion::Crypt::Settings.default
31
+ end
26
32
  end
27
33
 
28
34
  def shutdown
@@ -1,8 +1,11 @@
1
1
  require 'securerandom'
2
+ require 'legion/crypt/cluster_secret'
2
3
 
3
4
  module Legion
4
5
  module Crypt
5
6
  module Cipher
7
+ include Legion::Crypt::ClusterSecret
8
+
6
9
  def encrypt(message)
7
10
  cipher = OpenSSL::Cipher.new('aes-256-cbc')
8
11
  cipher.encrypt
@@ -40,50 +43,11 @@ module Legion
40
43
  end
41
44
 
42
45
  def private_key
43
- @private_key ||= OpenSSL::PKey::RSA.new 2048
44
- end
45
-
46
- def cs
47
- @cs ||= Digest::SHA256.digest fetch_cs
48
- end
49
-
50
- def fetch_cs # rubocop:disable Metrics/AbcSize
51
- if Legion::Settings[:crypt][:vault][:read_cluster_secret] && Legion::Settings[:crypt][:vault][:connected] && Legion::Crypt.exist?('crypt') # rubocop:disable Layout/LineLength
52
- Legion::Crypt.get('crypt')[:cluster_secret]
53
- elsif Legion::Settings[:crypt][:cluster_secret].is_a? String
54
- Legion::Settings[:crypt][:cluster_secret]
55
- elsif Legion::Transport::Queue.new('node.crypt', passive: true).consumer_count.zero?
56
- Legion::Settings[:crypt][:cluster_secret] = generate_secure_random
57
- elsif Legion::Transport::Queue.new('node.crypt', passive: true).consumer_count.positive?
58
- require 'legion/transport/messages/request_cluster_secret'
59
- Legion::Logging.info 'Requesting cluster secret via public key'
60
- start = Time.now
61
- Legion::Transport::Messages::RequestClusterSecret.new.publish
62
- sleep_time = 0.001
63
- until !Legion::Settings[:crypt][:cluster_secret].nil? || (Time.now - start) > Legion::Settings[:crypt][:cluster_secret_timeout]
64
- sleep(sleep_time)
65
- sleep_time *= 2 unless sleep_time > 0.5
66
- end
67
- unless Legion::Settings[:crypt][:cluster_secret].nil?
68
- Legion::Logging.info "Received cluster secret in #{((Time.new - start) * 1000.0).round}ms"
69
- end
70
- Legion::Logging.warn 'Cluster secret is still nil' if Legion::Settings[:crypt][:cluster_secret].nil?
71
- else
72
- Legion::Settings[:crypt][:cluster_secret] = generate_secure_random
73
- end
74
- Legion::Settings[:crypt][:cs_encrypt_ready] = true
75
- Legion::Settings[:crypt][:cluster_secret]
76
- rescue StandardError => e
77
- Legion::Logging.error(e.message)
78
- Legion::Logging.error(e.backtrace)
79
-
80
- Legion::Settings[:crypt][:cluster_secret] = generate_secure_random
81
- Legion::Settings[:crypt][:cs_encrypt_ready] = true
82
- Legion::Settings[:crypt][:cluster_secret]
83
- end
84
-
85
- def generate_secure_random
86
- SecureRandom.alphanumeric(32)
46
+ @private_key ||= if Legion::Settings[:crypt][:read_private_key] && File.exist?('./legionio.key')
47
+ OpenSSL::PKey::RSA.new File.read './legionio.key'
48
+ else
49
+ OpenSSL::PKey::RSA.new 2048
50
+ end
87
51
  end
88
52
  end
89
53
  end
@@ -0,0 +1,121 @@
1
+ require 'securerandom'
2
+
3
+ module Legion
4
+ module Crypt
5
+ module ClusterSecret
6
+ def find_cluster_secret
7
+ %i[from_settings from_vault from_transport generate_secure_random].each do |method|
8
+ result = send(method)
9
+ next if result.nil?
10
+
11
+ unless validate_hex(result)
12
+ Legion::Logging.warn("Legion::Crypt.#{method} gave a value but it isn't a valid hex")
13
+ next
14
+ end
15
+
16
+ set_cluster_secret(result, method != :from_vault)
17
+ return result
18
+ end
19
+ return unless only_member?
20
+
21
+ key = generate_secure_random
22
+ set_cluster_secret(key)
23
+ key
24
+ end
25
+
26
+ def from_vault
27
+ return nil unless method_defined? :get
28
+ return nil unless Legion::Settings[:crypt][:vault][:read_cluster_secret]
29
+ return nil unless Legion::Settings[:crypt][:vault][:connected]
30
+ return nil unless Legion::Crypt.exist?('crypt')
31
+
32
+ get('crypt')[:cluster_secret]
33
+ rescue StandardError
34
+ nil
35
+ end
36
+
37
+ def from_settings
38
+ Legion::Settings[:crypt][:cluster_secret]
39
+ end
40
+ alias cluster_secret from_settings
41
+
42
+ def from_transport # rubocop:disable Metrics/AbcSize
43
+ return nil unless Legion::Settings[:transport][:connected]
44
+
45
+ require 'legion/transport/messages/request_cluster_secret'
46
+ Legion::Logging.info 'Requesting cluster secret via public key'
47
+ Legion::Logging.warn 'cluster_secret already set but we are requesting a new value' unless from_settings.nil?
48
+ start = Time.now
49
+ Legion::Transport::Messages::RequestClusterSecret.new.publish
50
+ sleep_time = 0.001
51
+ until !Legion::Settings[:crypt][:cluster_secret].nil? || (Time.now - start) > cluster_secret_timeout
52
+ sleep(sleep_time)
53
+ sleep_time *= 2 unless sleep_time > 0.5
54
+ end
55
+
56
+ unless from_settings.nil?
57
+ Legion::Logging.info "Received cluster secret in #{((Time.new - start) * 1000.0).round}ms"
58
+ from_settings
59
+ end
60
+
61
+ Legion::Logging.error 'Cluster secret is still unknown!'
62
+ rescue StandardError => e
63
+ Legion::Logging.error e.message
64
+ Legion::Logging.error e.backtrace[0..10]
65
+ end
66
+
67
+ def force_cluster_secret
68
+ Legion::Settings[:crypt][:force_cluster_secret] || true
69
+ end
70
+
71
+ def settings_push_vault
72
+ Legion::Settings[:crypt][:vault][:push_cs_to_vault] || true
73
+ end
74
+
75
+ def only_member?
76
+ Legion::Transport::Queue.new('node.crypt', passive: true).consumer_count.zero?
77
+ rescue StandardError
78
+ nil
79
+ end
80
+
81
+ def set_cluster_secret(value, push_to_vault = true) # rubocop:disable Style/OptionalBooleanParameter
82
+ raise TypeError unless value.to_i(32).to_s(32) == value.downcase
83
+
84
+ Legion::Settings[:crypt][:cs_encrypt_ready] = true
85
+ push_cs_to_vault if push_to_vault && settings_push_vault
86
+
87
+ Legion::Settings[:crypt][:cluster_secret] = value
88
+ end
89
+
90
+ def push_cs_to_vault
91
+ return false unless Legion::Settings[:crypt][:vault][:connected] && Legion::Settings[:crypt][:cluster_secret]
92
+
93
+ Legion::Logging.info 'Pushing Cluster Secret to Vault'
94
+ Legion::Crypt.write('cluster', secret: Legion::Settings[:crypt][:cluster_secret])
95
+ end
96
+
97
+ def cluster_secret_timeout
98
+ Legion::Settings[:crypt][:cluster_secret_timeout] || 5
99
+ end
100
+
101
+ def secret_length
102
+ Legion::Settings[:crypt][:cluster_lenth] || 32
103
+ end
104
+
105
+ def generate_secure_random(length = secret_length)
106
+ SecureRandom.hex(length)
107
+ end
108
+
109
+ def cs
110
+ @cs ||= Digest::SHA256.digest(find_cluster_secret)
111
+ rescue StandardError => e
112
+ Legion::Logging.error e.message
113
+ Legion::Logging.error e.backtrace[0..10]
114
+ end
115
+
116
+ def validate_hex(value, length = secret_length)
117
+ value.to_i(length).to_s(length) == value.downcase
118
+ end
119
+ end
120
+ end
121
+ end
@@ -3,28 +3,42 @@ module Legion
3
3
  module Settings
4
4
  def self.default
5
5
  {
6
- vault: vault,
6
+ vault: vault,
7
7
  cs_encrypt_ready: false,
8
- dynamic_keys: true
8
+ dynamic_keys: true,
9
+ cluster_secret: nil,
10
+ save_private_key: true,
11
+ read_private_key: true
9
12
  }
10
13
  end
11
14
 
12
15
  def self.vault
13
16
  {
14
- enabled: !Gem::Specification.find_by_name('vault').nil?,
15
- protocol: 'http',
16
- address: 'localhost',
17
- port: 8200,
18
- token: ENV['VAULT_DEV_ROOT_TOKEN_ID'] || ENV['VAULT_TOKEN_ID'] || nil,
19
- connected: false,
20
- renewer_time: 5,
21
- renewer: true,
17
+ enabled: !Gem::Specification.find_by_name('vault').nil?,
18
+ protocol: 'http',
19
+ address: 'localhost',
20
+ port: 8200,
21
+ token: ENV['VAULT_DEV_ROOT_TOKEN_ID'] || ENV['VAULT_TOKEN_ID'] || nil,
22
+ connected: false,
23
+ renewer_time: 5,
24
+ renewer: true,
22
25
  push_cluster_secret: true,
23
- read_cluster_secret: true
26
+ read_cluster_secret: true,
27
+ kv_path: ENV['LEGION_VAULT_KV_PATH'] || 'legion'
24
28
  }
25
29
  end
26
30
  end
27
31
  end
28
32
  end
29
33
 
30
- Legion::Settings.merge_settings('crypt', Legion::Crypt::Settings.default) if Legion.const_defined?('Settings')
34
+ begin
35
+ Legion::Settings.merge_settings('crypt', Legion::Crypt::Settings.default) if Legion.const_defined?('Settings')
36
+ rescue StandardError => e
37
+ if Legion.const_defined?('Logging') && Legion::Logging.method_defined?(:fatal)
38
+ Legion::Logging.fatal(e.message)
39
+ Legion::Logging.fatal(e.backtrace)
40
+ else
41
+ puts e.message
42
+ puts e.backtrace
43
+ end
44
+ end