legion-crypt 1.4.28 → 1.5.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/CHANGELOG.md +29 -0
- data/Gemfile +1 -0
- data/legion-crypt.gemspec +1 -0
- data/lib/legion/crypt/attestation.rb +18 -8
- data/lib/legion/crypt/cert_rotation.rb +54 -42
- data/lib/legion/crypt/cipher.rb +106 -15
- data/lib/legion/crypt/cluster_secret.rb +41 -39
- data/lib/legion/crypt/ed25519.rb +58 -18
- data/lib/legion/crypt/erasure.rb +21 -8
- data/lib/legion/crypt/jwks_client.rb +37 -9
- data/lib/legion/crypt/jwt.rb +75 -31
- data/lib/legion/crypt/kerberos_auth.rb +23 -13
- data/lib/legion/crypt/ldap_auth.rb +12 -4
- data/lib/legion/crypt/lease_manager.rb +126 -73
- data/lib/legion/crypt/mtls.rb +14 -1
- data/lib/legion/crypt/partition_keys.rb +15 -5
- data/lib/legion/crypt/settings.rb +18 -12
- data/lib/legion/crypt/spiffe/identity_helpers.rb +18 -11
- data/lib/legion/crypt/spiffe/svid_rotation.rb +23 -33
- data/lib/legion/crypt/spiffe/workload_api_client.rb +61 -17
- data/lib/legion/crypt/spiffe.rb +18 -4
- data/lib/legion/crypt/tls.rb +14 -10
- data/lib/legion/crypt/token_renewer.rb +29 -26
- data/lib/legion/crypt/vault.rb +57 -45
- data/lib/legion/crypt/vault_cluster.rb +35 -17
- data/lib/legion/crypt/vault_jwt_auth.rb +17 -4
- data/lib/legion/crypt/vault_kerberos_auth.rb +11 -1
- data/lib/legion/crypt/version.rb +1 -1
- data/lib/legion/crypt.rb +69 -32
- data/lib/legion/logging/helper.rb +98 -0
- data/lib/legion/logging.rb +58 -0
- metadata +17 -1
data/lib/legion/crypt.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'openssl'
|
|
4
4
|
require 'base64'
|
|
5
|
+
require 'legion/logging/helper'
|
|
5
6
|
require 'legion/crypt/version'
|
|
6
7
|
require 'legion/crypt/settings'
|
|
7
8
|
require 'legion/crypt/cipher'
|
|
@@ -27,6 +28,7 @@ module Legion
|
|
|
27
28
|
class << self
|
|
28
29
|
attr_reader :sessions
|
|
29
30
|
|
|
31
|
+
include Legion::Logging::Helper
|
|
30
32
|
include Legion::Crypt::Cipher
|
|
31
33
|
|
|
32
34
|
unless Gem::Specification.find_by_name('vault').nil?
|
|
@@ -57,18 +59,30 @@ module Legion
|
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
def start
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
lifecycle_mutex.synchronize do
|
|
63
|
+
if @started
|
|
64
|
+
log.info 'Legion::Crypt start ignored because the lifecycle is already running'
|
|
65
|
+
return
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
log.info 'Legion::Crypt startup initiated'
|
|
69
|
+
log.debug 'Legion::Crypt start requested'
|
|
70
|
+
::File.write('./legionio.key', private_key) if settings[:save_private_key]
|
|
71
|
+
@token_renewers ||= []
|
|
72
|
+
|
|
73
|
+
if vault_settings[:clusters]&.any?
|
|
74
|
+
log.info "Legion::Crypt connecting #{vault_settings[:clusters].size} Vault cluster(s)"
|
|
75
|
+
connect_all_clusters
|
|
76
|
+
start_token_renewers
|
|
77
|
+
else
|
|
78
|
+
log.info 'Legion::Crypt connecting primary Vault client' unless settings[:vault][:token].nil?
|
|
79
|
+
connect_vault unless settings[:vault][:token].nil?
|
|
80
|
+
end
|
|
81
|
+
start_lease_manager
|
|
82
|
+
start_svid_rotation
|
|
83
|
+
@started = true
|
|
84
|
+
log.info 'Legion::Crypt startup completed'
|
|
69
85
|
end
|
|
70
|
-
start_lease_manager
|
|
71
|
-
start_svid_rotation
|
|
72
86
|
end
|
|
73
87
|
|
|
74
88
|
def settings
|
|
@@ -83,6 +97,16 @@ module Legion
|
|
|
83
97
|
settings[:jwt] || Legion::Crypt::Settings.jwt
|
|
84
98
|
end
|
|
85
99
|
|
|
100
|
+
def vault_connected?
|
|
101
|
+
return true if settings.dig(:vault, :connected) == true
|
|
102
|
+
return true if respond_to?(:connected_clusters) && connected_clusters.any?
|
|
103
|
+
|
|
104
|
+
false
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
handle_exception(e, level: :debug, operation: 'crypt.vault_connected?')
|
|
107
|
+
false
|
|
108
|
+
end
|
|
109
|
+
|
|
86
110
|
def issue_token(payload = {}, ttl: nil, algorithm: nil)
|
|
87
111
|
jwt = jwt_settings
|
|
88
112
|
algo = algorithm || jwt[:default_algorithm]
|
|
@@ -111,11 +135,21 @@ module Legion
|
|
|
111
135
|
end
|
|
112
136
|
|
|
113
137
|
def shutdown
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
lifecycle_mutex.synchronize do
|
|
139
|
+
unless @started
|
|
140
|
+
log.info 'Legion::Crypt shutdown ignored because the lifecycle is not running'
|
|
141
|
+
return
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
log.info 'Legion::Crypt shutdown initiated'
|
|
145
|
+
Legion::Crypt::LeaseManager.instance.shutdown
|
|
146
|
+
stop_token_renewers
|
|
147
|
+
shutdown_renewer
|
|
148
|
+
close_sessions
|
|
149
|
+
stop_svid_rotation
|
|
150
|
+
@started = false
|
|
151
|
+
log.info 'Legion::Crypt shutdown completed'
|
|
152
|
+
end
|
|
119
153
|
end
|
|
120
154
|
|
|
121
155
|
private
|
|
@@ -123,22 +157,15 @@ module Legion
|
|
|
123
157
|
def start_lease_manager
|
|
124
158
|
leases = settings.dig(:vault, :leases) || {}
|
|
125
159
|
return if leases.empty?
|
|
126
|
-
return unless settings.dig(:vault, :connected)
|
|
160
|
+
return unless connected_clusters.any? || settings.dig(:vault, :connected)
|
|
127
161
|
|
|
128
162
|
client = nil
|
|
129
163
|
|
|
130
|
-
if
|
|
131
|
-
|
|
132
|
-
elsif connected_clusters.any?
|
|
133
|
-
default_cluster = vault_settings[:default]
|
|
134
|
-
selected_cluster =
|
|
135
|
-
if default_cluster && connected_clusters.include?(default_cluster.to_sym)
|
|
136
|
-
default_cluster.to_sym
|
|
137
|
-
else
|
|
138
|
-
connected_clusters.keys.first
|
|
139
|
-
end
|
|
140
|
-
|
|
164
|
+
if connected_clusters.any?
|
|
165
|
+
selected_cluster = selected_connected_cluster_name
|
|
141
166
|
client = selected_cluster ? vault_client(selected_cluster) : nil
|
|
167
|
+
elsif settings.dig(:vault, :connected)
|
|
168
|
+
client = vault_client
|
|
142
169
|
end
|
|
143
170
|
lease_manager = Legion::Crypt::LeaseManager.instance
|
|
144
171
|
lease_manager.start(leases, vault_client: client)
|
|
@@ -146,15 +173,16 @@ module Legion
|
|
|
146
173
|
fetched = lease_manager.fetched_count
|
|
147
174
|
defined = leases.size
|
|
148
175
|
if fetched == defined
|
|
149
|
-
|
|
176
|
+
log.info "LeaseManager: #{fetched} lease(s) initialized"
|
|
150
177
|
else
|
|
151
|
-
|
|
178
|
+
log.warn "LeaseManager: #{fetched}/#{defined} lease(s) initialized (#{defined - fetched} failed)"
|
|
152
179
|
end
|
|
153
180
|
rescue StandardError => e
|
|
154
|
-
|
|
181
|
+
handle_exception(e, level: :warn, operation: 'crypt.start_lease_manager')
|
|
155
182
|
end
|
|
156
183
|
|
|
157
184
|
def start_token_renewers
|
|
185
|
+
started = 0
|
|
158
186
|
clusters.each do |name, config|
|
|
159
187
|
next unless config[:auth_method]&.to_s == 'kerberos' && config[:connected]
|
|
160
188
|
|
|
@@ -165,31 +193,40 @@ module Legion
|
|
|
165
193
|
)
|
|
166
194
|
renewer.start
|
|
167
195
|
@token_renewers << renewer
|
|
196
|
+
started += 1
|
|
168
197
|
end
|
|
198
|
+
log.info "Legion::Crypt started #{started} token renewer(s)" if started.positive?
|
|
169
199
|
end
|
|
170
200
|
|
|
171
201
|
def stop_token_renewers
|
|
172
202
|
return unless @token_renewers
|
|
173
203
|
|
|
174
204
|
@token_renewers.each(&:stop)
|
|
205
|
+
log.info "Legion::Crypt stopped #{@token_renewers.size} token renewer(s)" if @token_renewers.any?
|
|
175
206
|
@token_renewers.clear
|
|
176
207
|
end
|
|
177
208
|
|
|
178
209
|
def start_svid_rotation
|
|
179
210
|
return unless Spiffe.enabled?
|
|
180
211
|
|
|
212
|
+
log.info 'Legion::Crypt starting SPIFFE SVID rotation'
|
|
181
213
|
@svid_rotation = Spiffe::SvidRotation.new
|
|
182
214
|
@svid_rotation.start
|
|
183
215
|
rescue StandardError => e
|
|
184
|
-
|
|
216
|
+
handle_exception(e, level: :warn, operation: 'crypt.start_svid_rotation')
|
|
185
217
|
end
|
|
186
218
|
|
|
187
219
|
def stop_svid_rotation
|
|
188
220
|
return unless @svid_rotation
|
|
189
221
|
|
|
222
|
+
log.info 'Legion::Crypt stopping SPIFFE SVID rotation'
|
|
190
223
|
@svid_rotation.stop
|
|
191
224
|
@svid_rotation = nil
|
|
192
225
|
end
|
|
226
|
+
|
|
227
|
+
def lifecycle_mutex
|
|
228
|
+
@lifecycle_mutex ||= Mutex.new
|
|
229
|
+
end
|
|
193
230
|
end
|
|
194
231
|
end
|
|
195
232
|
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
helper_path = File.join(
|
|
5
|
+
Gem::Specification.find_by_name('legion-logging').full_gem_path,
|
|
6
|
+
'lib/legion/logging/helper.rb'
|
|
7
|
+
)
|
|
8
|
+
require helper_path if File.exist?(helper_path)
|
|
9
|
+
rescue Gem::LoadError
|
|
10
|
+
nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require 'legion/logging'
|
|
14
|
+
|
|
15
|
+
module Legion
|
|
16
|
+
module Logging
|
|
17
|
+
module Helper
|
|
18
|
+
unless const_defined?(:CompatLogger, false)
|
|
19
|
+
CompatLogger = Class.new do
|
|
20
|
+
%i[debug info warn error fatal unknown].each do |level|
|
|
21
|
+
define_method(level) do |message = nil, &block|
|
|
22
|
+
payload = block ? block.call : message
|
|
23
|
+
return if payload.nil?
|
|
24
|
+
|
|
25
|
+
if logging_supports?(level)
|
|
26
|
+
Legion::Logging.public_send(level, payload)
|
|
27
|
+
elsif %i[error fatal warn].include?(level)
|
|
28
|
+
::Kernel.warn(payload)
|
|
29
|
+
else
|
|
30
|
+
$stdout.puts(payload)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def logging_supports?(level)
|
|
38
|
+
return false unless Legion.const_defined?('Logging')
|
|
39
|
+
|
|
40
|
+
Legion::Logging.respond_to?(level)
|
|
41
|
+
rescue StandardError
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def log
|
|
48
|
+
@log ||= CompatLogger.new
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def handle_exception(exception, task_id: nil, level: :error, handled: true, **opts) # rubocop:disable Lint/UnusedMethodArgument,Style/ArgumentsForwarding
|
|
52
|
+
message = exception_log_message(exception, level: level, **opts) # rubocop:disable Style/ArgumentsForwarding
|
|
53
|
+
|
|
54
|
+
if logging_supports?(:log_exception)
|
|
55
|
+
Legion::Logging.log_exception(exception, lex: 'crypt', component_type: :helper)
|
|
56
|
+
return
|
|
57
|
+
end
|
|
58
|
+
if logging_supports?(level)
|
|
59
|
+
Legion::Logging.public_send(level, message)
|
|
60
|
+
return
|
|
61
|
+
end
|
|
62
|
+
if logging_supports?(:error)
|
|
63
|
+
Legion::Logging.error(message)
|
|
64
|
+
return
|
|
65
|
+
end
|
|
66
|
+
if logging_supports?(:warn)
|
|
67
|
+
Legion::Logging.warn(message)
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
::Kernel.warn(message)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def logging_supports?(level)
|
|
77
|
+
return false unless Legion.const_defined?('Logging')
|
|
78
|
+
|
|
79
|
+
Legion::Logging.respond_to?(level)
|
|
80
|
+
rescue StandardError
|
|
81
|
+
false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def exception_log_message(exception, level:, **opts)
|
|
85
|
+
operation = opts[:operation] || opts['operation']
|
|
86
|
+
prefix = operation ? "#{operation} failed: " : ''
|
|
87
|
+
details = opts.reject { |key, _value| key.to_s == 'operation' }.map { |key, value| "#{key}=#{value}" }
|
|
88
|
+
detail_suffix = details.empty? ? '' : " (#{details.join(' ')})"
|
|
89
|
+
backtrace = Array(exception.backtrace).first(10).join("\n")
|
|
90
|
+
base = "#{prefix}#{exception.class}: #{exception.message}#{detail_suffix}"
|
|
91
|
+
return base if backtrace.empty? && level == :debug
|
|
92
|
+
return base if backtrace.empty?
|
|
93
|
+
|
|
94
|
+
"#{base}\n#{backtrace}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
gem_root = Gem::Specification.find_by_name('legion-logging').full_gem_path
|
|
7
|
+
upstream_logging = File.join(gem_root, 'lib/legion/logging.rb')
|
|
8
|
+
require upstream_logging if File.exist?(upstream_logging)
|
|
9
|
+
rescue Gem::LoadError
|
|
10
|
+
nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module Legion
|
|
14
|
+
module Logging
|
|
15
|
+
class << self
|
|
16
|
+
unless method_defined?(:setup)
|
|
17
|
+
def setup(level: 'info', **_opts)
|
|
18
|
+
logger.level = normalize_level(level)
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def logger
|
|
23
|
+
@logger ||= Logger.new($stdout).tap do |instance|
|
|
24
|
+
instance.progname = 'legion-crypt'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def log_exception(exception, lex: nil, component_type: nil, **_opts)
|
|
29
|
+
prefix = [lex, component_type].compact.join('.')
|
|
30
|
+
payload = prefix.empty? ? exception.message : "#{prefix}: #{exception.message}"
|
|
31
|
+
error(payload)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
%i[debug info warn error fatal unknown].each do |level_name|
|
|
35
|
+
define_method(level_name) do |message = nil, &block|
|
|
36
|
+
payload = block ? block.call : message
|
|
37
|
+
return if payload.nil?
|
|
38
|
+
|
|
39
|
+
logger.public_send(level_name, payload)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def normalize_level(level)
|
|
46
|
+
case level.to_s.downcase
|
|
47
|
+
when 'debug' then Logger::DEBUG
|
|
48
|
+
when 'info' then Logger::INFO
|
|
49
|
+
when 'warn' then Logger::WARN
|
|
50
|
+
when 'error' then Logger::ERROR
|
|
51
|
+
when 'fatal' then Logger::FATAL
|
|
52
|
+
else Logger::UNKNOWN
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legion-crypt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '2.7'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: legion-logging
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 1.5.0
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 1.5.0
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: vault
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -103,6 +117,8 @@ files:
|
|
|
103
117
|
- lib/legion/crypt/vault_kerberos_auth.rb
|
|
104
118
|
- lib/legion/crypt/vault_renewer.rb
|
|
105
119
|
- lib/legion/crypt/version.rb
|
|
120
|
+
- lib/legion/logging.rb
|
|
121
|
+
- lib/legion/logging/helper.rb
|
|
106
122
|
- sonar-project.properties
|
|
107
123
|
homepage: https://github.com/LegionIO/legion-crypt
|
|
108
124
|
licenses:
|