legion-crypt 1.5.13 → 1.5.38
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/.rubocop.yml +3 -0
- data/CHANGELOG.md +145 -0
- data/README.md +74 -14
- data/legion-crypt.gemspec +1 -1
- data/lib/legion/crypt/cert_rotation.rb +83 -65
- data/lib/legion/crypt/cipher.rb +5 -15
- data/lib/legion/crypt/cluster_secret.rb +71 -19
- data/lib/legion/crypt/erasure.rb +3 -10
- data/lib/legion/crypt/jwks_client.rb +43 -31
- data/lib/legion/crypt/jwt.rb +8 -7
- data/lib/legion/crypt/kerberos_auth.rb +18 -8
- data/lib/legion/crypt/ldap_auth.rb +14 -13
- data/lib/legion/crypt/lease_manager.rb +648 -333
- data/lib/legion/crypt/mtls.rb +3 -6
- data/lib/legion/crypt/partition_keys.rb +2 -0
- data/lib/legion/crypt/settings.rb +2 -1
- data/lib/legion/crypt/spiffe/workload_api_client.rb +83 -71
- data/lib/legion/crypt/token_renewer.rb +212 -82
- data/lib/legion/crypt/vault.rb +113 -83
- data/lib/legion/crypt/vault_client.rb +355 -0
- data/lib/legion/crypt/vault_cluster.rb +161 -39
- data/lib/legion/crypt/vault_entity.rb +21 -34
- data/lib/legion/crypt/vault_jwt_auth.rb +9 -20
- data/lib/legion/crypt/vault_kerberos_auth.rb +11 -14
- data/lib/legion/crypt/vault_operation.rb +27 -0
- data/lib/legion/crypt/version.rb +1 -1
- data/lib/legion/crypt.rb +116 -63
- metadata +17 -15
data/lib/legion/crypt.rb
CHANGED
|
@@ -9,7 +9,9 @@ require 'legion/crypt/cipher'
|
|
|
9
9
|
require 'legion/crypt/jwt'
|
|
10
10
|
require 'legion/crypt/vault_jwt_auth'
|
|
11
11
|
require 'legion/crypt/lease_manager'
|
|
12
|
+
require 'legion/crypt/vault_operation'
|
|
12
13
|
require 'legion/crypt/vault_cluster'
|
|
14
|
+
require 'legion/crypt/vault'
|
|
13
15
|
require 'legion/crypt/ldap_auth'
|
|
14
16
|
require 'legion/crypt/token_renewer'
|
|
15
17
|
require 'legion/crypt/helper'
|
|
@@ -32,15 +34,12 @@ module Legion
|
|
|
32
34
|
}.freeze
|
|
33
35
|
|
|
34
36
|
class << self
|
|
35
|
-
attr_reader :sessions
|
|
37
|
+
attr_reader :sessions, :stopping
|
|
36
38
|
|
|
37
39
|
include Legion::Logging::Helper
|
|
38
40
|
include Legion::Crypt::Cipher
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
require 'legion/crypt/vault'
|
|
42
|
-
include Legion::Crypt::Vault
|
|
43
|
-
end
|
|
42
|
+
include Legion::Crypt::Vault
|
|
44
43
|
|
|
45
44
|
def vault_settings
|
|
46
45
|
Legion::Settings[:crypt][:vault]
|
|
@@ -66,24 +65,32 @@ module Legion
|
|
|
66
65
|
|
|
67
66
|
def start
|
|
68
67
|
lifecycle_mutex.synchronize do
|
|
69
|
-
if @started
|
|
70
|
-
log.info 'Legion::Crypt start ignored because the lifecycle is
|
|
68
|
+
if @started || @stopping
|
|
69
|
+
log.info 'Legion::Crypt start ignored because the lifecycle is running or stopping'
|
|
71
70
|
return
|
|
72
71
|
end
|
|
73
72
|
|
|
74
73
|
log.info 'Legion::Crypt startup initiated'
|
|
75
74
|
log.debug 'Legion::Crypt start requested'
|
|
76
|
-
|
|
75
|
+
if settings[:save_private_key]
|
|
76
|
+
key = private_key
|
|
77
|
+
::File.open('./legionio.key', ::File::WRONLY | ::File::CREAT, 0o600) do |file|
|
|
78
|
+
file.chmod(0o600)
|
|
79
|
+
file.truncate(0)
|
|
80
|
+
file.write(key.to_pem)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
77
83
|
@token_renewers ||= []
|
|
84
|
+
start_session_tracking
|
|
78
85
|
|
|
79
86
|
if vault_settings[:clusters]&.any?
|
|
80
87
|
log.info "Legion::Crypt connecting #{vault_settings[:clusters].size} Vault cluster(s)"
|
|
81
88
|
connect_all_clusters
|
|
82
|
-
start_token_renewers
|
|
83
89
|
else
|
|
84
90
|
log.info 'Legion::Crypt connecting primary Vault client' unless settings[:vault][:token].nil?
|
|
85
91
|
connect_vault unless settings[:vault][:token].nil?
|
|
86
92
|
end
|
|
93
|
+
start_token_renewers
|
|
87
94
|
start_lease_manager
|
|
88
95
|
start_svid_rotation
|
|
89
96
|
@started = true
|
|
@@ -91,6 +98,29 @@ module Legion
|
|
|
91
98
|
end
|
|
92
99
|
end
|
|
93
100
|
|
|
101
|
+
def vault_auth_generation
|
|
102
|
+
lifecycle_mutex.synchronize do
|
|
103
|
+
raise 'Crypt is shutting down; login must be retried' if @stopping
|
|
104
|
+
|
|
105
|
+
@token_generation || 0
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def install_vault_grant(client:, config:, grant:, auth_method:, generation:, **)
|
|
110
|
+
issued_at = Time.now
|
|
111
|
+
lifecycle_mutex.synchronize do
|
|
112
|
+
raise 'Crypt stopped during login; login must be retried' if @stopping || generation != (@token_generation || 0)
|
|
113
|
+
|
|
114
|
+
cluster_name = clusters.find { |_name, candidate| candidate.equal?(config) }&.first
|
|
115
|
+
previous = (@token_renewers || []).find { |worker| worker.cluster_name == cluster_name }
|
|
116
|
+
previous&.stop
|
|
117
|
+
raise 'Previous token worker has not stopped; login must be retried' if previous&.running?
|
|
118
|
+
|
|
119
|
+
publish_vault_grant(client: client, config: config, grant: grant, auth_method: auth_method, issued_at: issued_at)
|
|
120
|
+
start_token_renewer(cluster_name: cluster_name, config: config, vault_client: client)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
94
124
|
def settings
|
|
95
125
|
if Legion.const_defined?('Settings')
|
|
96
126
|
Legion::Settings[:crypt]
|
|
@@ -112,7 +142,7 @@ module Legion
|
|
|
112
142
|
|
|
113
143
|
Legion::Settings.merge_settings('transport', Legion::Transport::Settings.default) if defined?(Legion::Transport::Settings)
|
|
114
144
|
|
|
115
|
-
response = LeaseManager.instance.vault_logical.read('rabbitmq/creds/legionio-bootstrap')
|
|
145
|
+
response = LeaseManager.instance.vault_logical.read(path: 'rabbitmq/creds/legionio-bootstrap')
|
|
116
146
|
return unless response&.data
|
|
117
147
|
|
|
118
148
|
bootstrap_lease_ttl = Legion::Settings.dig(:crypt, :vault, :bootstrap_lease_ttl).to_i
|
|
@@ -147,10 +177,18 @@ module Legion
|
|
|
147
177
|
return if mode == :lite
|
|
148
178
|
|
|
149
179
|
role = RMQ_ROLE_MAP.fetch(mode, "legionio-#{mode}")
|
|
150
|
-
|
|
151
|
-
|
|
180
|
+
manager = LeaseManager.instance
|
|
181
|
+
response = Legion::Crypt::VaultOperation.with_timeout { manager.vault_logical.read(path: "rabbitmq/creds/#{role}") }
|
|
182
|
+
raise "Failed to fetch identity-scoped RMQ creds for role #{role}" unless response
|
|
152
183
|
|
|
153
|
-
|
|
184
|
+
settings = Legion::Settings.loader.settings
|
|
185
|
+
settings[:transport] ||= {}
|
|
186
|
+
settings[:transport][:connection] ||= {}
|
|
187
|
+
conn = settings[:transport][:connection]
|
|
188
|
+
conn[:user] = nil unless conn.key?(:user)
|
|
189
|
+
conn[:password] = nil unless conn.key?(:password)
|
|
190
|
+
|
|
191
|
+
installed = manager.register_dynamic_lease(
|
|
154
192
|
name: :rabbitmq,
|
|
155
193
|
path: "rabbitmq/creds/#{role}",
|
|
156
194
|
response: response,
|
|
@@ -159,32 +197,15 @@ module Legion
|
|
|
159
197
|
{ path: %i[transport connection password], key: :password }
|
|
160
198
|
]
|
|
161
199
|
)
|
|
200
|
+
raise 'Identity credential handoff failed — bootstrap lease NOT revoked' unless installed
|
|
162
201
|
|
|
163
|
-
|
|
164
|
-
settings[:transport] ||= {}
|
|
165
|
-
settings[:transport][:connection] ||= {}
|
|
166
|
-
conn = settings[:transport][:connection]
|
|
167
|
-
username = response.data[:username] || response.data['username']
|
|
168
|
-
password = response.data[:password] || response.data['password']
|
|
169
|
-
raise "Identity-scoped RMQ creds for role #{role} missing username or password" unless username && password
|
|
170
|
-
|
|
171
|
-
conn[:user] = username
|
|
172
|
-
conn[:password] = password
|
|
173
|
-
|
|
174
|
-
if defined?(Legion::Transport::Connection)
|
|
175
|
-
Legion::Transport::Connection.force_reconnect
|
|
176
|
-
raise 'Transport reconnect failed after credential swap — bootstrap lease NOT revoked' unless Legion::Transport::Connection.session_open?
|
|
177
|
-
|
|
178
|
-
log.info "Transport reconnected with identity-scoped creds (role: #{role})"
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
revoke_bootstrap_lease
|
|
202
|
+
Legion::Crypt::VaultOperation.with_timeout { revoke_bootstrap_lease }
|
|
182
203
|
end
|
|
183
204
|
|
|
184
205
|
def revoke_bootstrap_lease
|
|
185
206
|
return unless @bootstrap_lease_id
|
|
186
207
|
|
|
187
|
-
LeaseManager.instance.vault_sys.revoke(@bootstrap_lease_id)
|
|
208
|
+
LeaseManager.instance.vault_sys.revoke(lease_id: @bootstrap_lease_id)
|
|
188
209
|
log.info "Bootstrap RMQ lease revoked (#{@bootstrap_lease_id[0..7]}...)"
|
|
189
210
|
@bootstrap_lease_id = nil
|
|
190
211
|
@bootstrap_lease_expires = nil
|
|
@@ -196,6 +217,8 @@ module Legion
|
|
|
196
217
|
end
|
|
197
218
|
|
|
198
219
|
def vault_connected?
|
|
220
|
+
return connected_clusters.any? if settings.dig(:vault, :clusters)&.any?
|
|
221
|
+
|
|
199
222
|
return true if settings.dig(:vault, :connected) == true
|
|
200
223
|
return true if respond_to?(:connected_clusters) && connected_clusters.any?
|
|
201
224
|
|
|
@@ -233,20 +256,43 @@ module Legion
|
|
|
233
256
|
end
|
|
234
257
|
|
|
235
258
|
def shutdown
|
|
236
|
-
lifecycle_mutex.synchronize do
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return
|
|
240
|
-
end
|
|
259
|
+
shutdown_started = lifecycle_mutex.synchronize do
|
|
260
|
+
return if @stopping
|
|
261
|
+
return unless @started || @vault_clients || @retired_vault_clients&.size&.positive?
|
|
241
262
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
stop_token_renewers
|
|
245
|
-
shutdown_renewer
|
|
246
|
-
close_sessions
|
|
247
|
-
stop_svid_rotation
|
|
263
|
+
@stopping = true
|
|
264
|
+
@token_generation = (@token_generation || 0) + 1
|
|
248
265
|
@started = false
|
|
249
|
-
|
|
266
|
+
true
|
|
267
|
+
end
|
|
268
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + Legion::Crypt::VaultOperation::SHUTDOWN_TIMEOUT
|
|
269
|
+
manager = Legion::Crypt::LeaseManager.instance
|
|
270
|
+
[manager, *(@token_renewers || [])].each do |worker|
|
|
271
|
+
worker.request_stop
|
|
272
|
+
rescue StandardError => e
|
|
273
|
+
handle_exception(e, level: :warn, operation: 'crypt.shutdown.request_stop')
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
components = [
|
|
277
|
+
[self, :stop_svid_rotation, { deadline: deadline }],
|
|
278
|
+
[self, :shutdown_renewer, {}],
|
|
279
|
+
[manager, :shutdown, { deadline: deadline }],
|
|
280
|
+
[self, :stop_token_renewers, { deadline: deadline }],
|
|
281
|
+
[self, :close_sessions, { deadline: deadline }],
|
|
282
|
+
[self, :clear_vault_clients, { deadline: deadline }]
|
|
283
|
+
]
|
|
284
|
+
components.each do |owner, operation, options|
|
|
285
|
+
owner.send(operation, **options)
|
|
286
|
+
rescue StandardError => e
|
|
287
|
+
handle_exception(e, level: :warn, operation: "crypt.shutdown.#{operation}")
|
|
288
|
+
end
|
|
289
|
+
log.info 'Legion::Crypt shutdown completed'
|
|
290
|
+
ensure
|
|
291
|
+
if shutdown_started
|
|
292
|
+
lifecycle_mutex.synchronize do
|
|
293
|
+
@started = false
|
|
294
|
+
@stopping = false
|
|
295
|
+
end
|
|
250
296
|
end
|
|
251
297
|
end
|
|
252
298
|
|
|
@@ -287,27 +333,34 @@ module Legion
|
|
|
287
333
|
|
|
288
334
|
def start_token_renewers
|
|
289
335
|
started = 0
|
|
290
|
-
clusters.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
config: config,
|
|
296
|
-
vault_client: vault_client(name)
|
|
297
|
-
)
|
|
298
|
-
renewer.start
|
|
299
|
-
@token_renewers << renewer
|
|
336
|
+
configurations = clusters.empty? ? { nil => vault_settings } : clusters
|
|
337
|
+
configurations.each do |name, config|
|
|
338
|
+
next unless %w[kerberos ldap jwt].include?(config[:auth_method].to_s) && config[:connected]
|
|
339
|
+
|
|
340
|
+
start_token_renewer(cluster_name: name, config: config, vault_client: vault_client(name))
|
|
300
341
|
started += 1
|
|
301
342
|
end
|
|
302
343
|
log.info "Legion::Crypt started #{started} token renewer(s)" if started.positive?
|
|
303
344
|
end
|
|
304
345
|
|
|
305
|
-
def
|
|
306
|
-
|
|
346
|
+
def start_token_renewer(cluster_name:, config:, vault_client:, **)
|
|
347
|
+
@token_renewers ||= []
|
|
348
|
+
return if @token_renewers.any? { |worker| worker.cluster_name == cluster_name && worker.running? }
|
|
307
349
|
|
|
308
|
-
@token_renewers.
|
|
309
|
-
|
|
310
|
-
@token_renewers
|
|
350
|
+
@token_renewers.reject! { |worker| worker.cluster_name == cluster_name }
|
|
351
|
+
renewer = Legion::Crypt::TokenRenewer.new(cluster_name: cluster_name, config: config, vault_client: vault_client)
|
|
352
|
+
@token_renewers << renewer
|
|
353
|
+
renewer.start
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def stop_token_renewers(deadline: nil)
|
|
357
|
+
renewers = @token_renewers || []
|
|
358
|
+
@token_renewers = []
|
|
359
|
+
renewers.each do |renewer|
|
|
360
|
+
renewer.stop(deadline: deadline)
|
|
361
|
+
rescue StandardError => e
|
|
362
|
+
handle_exception(e, level: :warn, operation: 'crypt.stop_token_renewers')
|
|
363
|
+
end
|
|
311
364
|
end
|
|
312
365
|
|
|
313
366
|
def start_svid_rotation
|
|
@@ -320,11 +373,11 @@ module Legion
|
|
|
320
373
|
handle_exception(e, level: :warn, operation: 'crypt.start_svid_rotation')
|
|
321
374
|
end
|
|
322
375
|
|
|
323
|
-
def stop_svid_rotation
|
|
376
|
+
def stop_svid_rotation(deadline: nil)
|
|
324
377
|
return unless @svid_rotation
|
|
325
378
|
|
|
326
|
-
|
|
327
|
-
|
|
379
|
+
Legion::Crypt::VaultOperation.with_timeout(deadline: deadline) { @svid_rotation.stop }
|
|
380
|
+
ensure
|
|
328
381
|
@svid_rotation = nil
|
|
329
382
|
end
|
|
330
383
|
|
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.5.
|
|
4
|
+
version: 1.5.38
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -38,61 +38,61 @@ dependencies:
|
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '1.3'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
41
|
+
name: faraday
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '2.
|
|
46
|
+
version: '2.0'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '2.
|
|
53
|
+
version: '2.0'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
|
-
name:
|
|
55
|
+
name: jwt
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - ">="
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version:
|
|
60
|
+
version: '2.7'
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version:
|
|
67
|
+
version: '2.7'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: legion-
|
|
69
|
+
name: legion-json
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
72
|
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 1.
|
|
74
|
+
version: 1.2.0
|
|
75
75
|
type: :runtime
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 1.
|
|
81
|
+
version: 1.2.0
|
|
82
82
|
- !ruby/object:Gem::Dependency
|
|
83
|
-
name:
|
|
83
|
+
name: legion-logging
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
|
85
85
|
requirements:
|
|
86
86
|
- - ">="
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
|
-
version:
|
|
88
|
+
version: 1.5.0
|
|
89
89
|
type: :runtime
|
|
90
90
|
prerelease: false
|
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
92
|
requirements:
|
|
93
93
|
- - ">="
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
|
-
version:
|
|
95
|
+
version: 1.5.0
|
|
96
96
|
description: A gem used by the LegionIO framework for encryption
|
|
97
97
|
email:
|
|
98
98
|
- matthewdiverson@gmail.com
|
|
@@ -141,10 +141,12 @@ files:
|
|
|
141
141
|
- lib/legion/crypt/tls.rb
|
|
142
142
|
- lib/legion/crypt/token_renewer.rb
|
|
143
143
|
- lib/legion/crypt/vault.rb
|
|
144
|
+
- lib/legion/crypt/vault_client.rb
|
|
144
145
|
- lib/legion/crypt/vault_cluster.rb
|
|
145
146
|
- lib/legion/crypt/vault_entity.rb
|
|
146
147
|
- lib/legion/crypt/vault_jwt_auth.rb
|
|
147
148
|
- lib/legion/crypt/vault_kerberos_auth.rb
|
|
149
|
+
- lib/legion/crypt/vault_operation.rb
|
|
148
150
|
- lib/legion/crypt/vault_renewer.rb
|
|
149
151
|
- lib/legion/crypt/version.rb
|
|
150
152
|
- scripts/pre-commit-rubocop.sh
|