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.
- checksums.yaml +4 -4
- data/.github/workflows/rubocop-analysis.yml +41 -0
- data/.github/workflows/sourcehawk-scan.yml +20 -0
- data/.gitignore +5 -1
- data/.rubocop.yml +7 -13
- data/CHANGELOG.md +4 -0
- data/CODE_OF_CONDUCT.md +75 -0
- data/CONTRIBUTING.md +55 -0
- data/Gemfile +7 -3
- data/INDIVIDUAL_CONTRIBUTOR_LICENSE.md +30 -0
- data/LICENSE +201 -0
- data/NOTICE.txt +9 -0
- data/README.md +43 -31
- data/SECURITY.md +9 -0
- data/attribution.txt +1 -0
- data/legion-crypt.gemspec +19 -26
- data/lib/legion/crypt.rb +16 -10
- data/lib/legion/crypt/cipher.rb +8 -44
- data/lib/legion/crypt/cluster_secret.rb +121 -0
- data/lib/legion/crypt/settings.rb +26 -12
- data/lib/legion/crypt/vault.rb +15 -8
- data/lib/legion/crypt/version.rb +1 -1
- data/sonar-project.properties +11 -0
- data/sourcehawk.yml +4 -0
- metadata +36 -98
- data/.circleci/config.yml +0 -61
- data/.idea/.rakeTasks +0 -7
- data/.idea/legion-crypt.iml +0 -54
- data/.idea/misc.xml +0 -7
- data/.idea/modules.xml +0 -8
- data/.idea/vagrant.xml +0 -7
- data/.idea/workspace.xml +0 -14
- data/.rspec +0 -3
- data/LICENSE.txt +0 -21
- data/Rakefile +0 -8
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/lib/legion/crypt/box.rb +0 -95
data/NOTICE.txt
ADDED
data/README.md
CHANGED
@@ -1,44 +1,56 @@
|
|
1
|
-
|
1
|
+
Legion::Crypt
|
2
|
+
=====
|
2
3
|
|
3
|
-
|
4
|
+
Legion::Crypt is the class responsible for encryption, managing secrets and connecting with Vault
|
4
5
|
|
5
|
-
|
6
|
+
Supported Ruby versions and implementations
|
7
|
+
------------------------------------------------
|
6
8
|
|
7
|
-
|
9
|
+
Legion::Crypt should work identically on:
|
8
10
|
|
9
|
-
|
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
|
-
|
15
|
+
Installation and Usage
|
16
|
+
------------------------
|
24
17
|
|
25
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
24
|
+
```ruby
|
25
|
+
require 'legion/crypt'
|
38
26
|
|
39
|
-
|
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
|
-
|
53
|
+
Authors
|
54
|
+
----------
|
43
55
|
|
44
|
-
|
56
|
+
* [Matthew Iverson](https://github.com/Esity) - current maintainer
|
data/SECURITY.md
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
|
6
|
+
spec.name = 'legion-crypt'
|
7
7
|
spec.version = Legion::Crypt::VERSION
|
8
8
|
spec.authors = ['Esity']
|
9
|
-
spec.email = [
|
10
|
-
|
11
|
-
spec.
|
12
|
-
spec.
|
13
|
-
spec.
|
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 '
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/legion/crypt/cipher.rb
CHANGED
@@ -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 ||=
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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:
|
6
|
+
vault: vault,
|
7
7
|
cs_encrypt_ready: false,
|
8
|
-
dynamic_keys:
|
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:
|
15
|
-
protocol:
|
16
|
-
address:
|
17
|
-
port:
|
18
|
-
token:
|
19
|
-
connected:
|
20
|
-
renewer_time:
|
21
|
-
renewer:
|
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
|
-
|
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
|