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.
- 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
|