legion-crypt 0.2.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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