legion-crypt 1.5.1 → 1.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a988afa9a1eca8337d8355120b5c5b7ea5a7feb5333477db6c3d22b1a9714bc2
4
- data.tar.gz: '068dce6425c3bd706848b2daab7a8465b1b1a7ffe5aa207b386b6263685b2db6'
3
+ metadata.gz: d834ae88ede665d60615cb9ae6eb4bda06060f85e2ed1bb965d8552a85b4f7cc
4
+ data.tar.gz: a2a06dd4523cea1ca8e3b93edebf96c1ab7214ce18f5cba0c7be0c8bfb047e46
5
5
  SHA512:
6
- metadata.gz: 6467a8507feb5172ea8fc081ddfe30fc82fd7fff7591a24ea0e10bd822a1939e326d98b7b6e8c9efdb8efe0ae4eb7e58b1ac9973beba5bf880db77a37589183a
7
- data.tar.gz: c81cadc3cdc2cbbc78d51dcc8884b1f4a3259c00383f7ead23208443bf852cf606489e980bd01b2a3887d69f0b1c9de5aff6a889c33632a2c1c4a8172daaebf5
6
+ metadata.gz: f136dd1740169138a66a2d6167d799c721632ced3a637cebb16e80517408cb70f6d4ba71b875bddcae109e5f9f63a7da15926feda9bf0045d852f32b33f34feb
7
+ data.tar.gz: 1029cf89058f5edfab6276087c39510ffcb734065aa0e75abd34344457ac25075780677d9c7c46c11ab7ae9035fab862183ee394eb2f86961fa4154571bec618
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Legion::Crypt
2
2
 
3
+ ## [1.5.3] - 2026-04-06
4
+
5
+ ### Added
6
+ - `JwksClient.prefetch!(url)` — fire-and-forget JWKS key fetch in background thread
7
+ - `JwksClient.start_background_refresh!(url, interval:)` — `Concurrent::TimerTask` for hourly key refresh
8
+ - `JwksClient.stop_background_refresh!` — stops background refresh timer task
9
+ - `bootstrap_lease_ttl: 300` in vault defaults (5-minute TTL for bootstrap credentials)
10
+
11
+ ### Changed
12
+ - `JwksClient.clear_cache` now also stops any running background refresh task
13
+
14
+ ## [1.5.2] - 2026-04-03
15
+
16
+ ### Fixed
17
+ - LeaseManager `at_exit` hook now wraps shutdown in a 10s timeout to prevent process hang when Logger Monitor or network I/O is blocked during crash exit
18
+
3
19
  ## [1.5.1] - 2026-04-03
4
20
 
5
21
  ### Fixed
data/legion-crypt.gemspec CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  'rubygems_mfa_required' => 'true'
26
26
  }
27
27
 
28
+ spec.add_dependency 'concurrent-ruby', '~> 1.3'
28
29
  spec.add_dependency 'ed25519', '~> 1.3'
29
30
  spec.add_dependency 'jwt', '>= 2.7'
30
31
  spec.add_dependency 'legion-logging', '>= 1.5.0'
@@ -6,6 +6,7 @@ require 'json'
6
6
  require 'openssl'
7
7
  require 'jwt'
8
8
  require 'legion/logging/helper'
9
+ require 'concurrent'
9
10
 
10
11
  module Legion
11
12
  module Crypt
@@ -56,7 +57,33 @@ module Legion
56
57
  raise Legion::Crypt::JWT::InvalidTokenError, "signing key not found: #{kid}"
57
58
  end
58
59
 
60
+ def prefetch!(jwks_url)
61
+ Thread.new do
62
+ fetch_keys(jwks_url)
63
+ rescue StandardError => e
64
+ log.debug "JWKS prefetch failed for #{jwks_url}: #{e.message}" if respond_to?(:log)
65
+ end
66
+ end
67
+
68
+ def start_background_refresh!(jwks_url, interval: CACHE_TTL)
69
+ stop_background_refresh!
70
+
71
+ @refresh_task = Concurrent::TimerTask.new(execution_interval: interval, run_now: false) do
72
+ fetch_keys(jwks_url)
73
+ rescue StandardError => e
74
+ log.debug "JWKS background refresh failed: #{e.message}" if respond_to?(:log)
75
+ end
76
+ @refresh_task.execute
77
+ log.info "JWKS background refresh started (interval=#{interval}s)" if respond_to?(:log)
78
+ end
79
+
80
+ def stop_background_refresh!
81
+ @refresh_task&.shutdown
82
+ @refresh_task = nil
83
+ end
84
+
59
85
  def clear_cache
86
+ stop_background_refresh!
60
87
  @cache_mutex.synchronize { @cache = {} }
61
88
  @locks_mutex.synchronize { @locks = {} }
62
89
  log.info 'JWKS cache cleared'
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'legion/logging/helper'
4
4
  require 'singleton'
5
+ require 'timeout'
5
6
 
6
7
  module Legion
7
8
  module Crypt
@@ -45,16 +46,8 @@ module Legion
45
46
  next
46
47
  end
47
48
 
48
- @state_mutex.synchronize do
49
- @lease_cache[name] = response.data || {}
50
- @active_leases[name] = {
51
- lease_id: response.lease_id,
52
- lease_duration: response.lease_duration,
53
- renewable: response.renewable?,
54
- expires_at: Time.now + (response.lease_duration || 0),
55
- fetched_at: Time.now
56
- }
57
- end
49
+ log_lease_response(name, response)
50
+ cache_lease(name, response)
58
51
  log.info("LeaseManager: fetched lease for '#{name}' from #{path}")
59
52
  rescue StandardError => e
60
53
  handle_exception(e, level: :warn, operation: 'crypt.lease_manager.start', lease_name: name, path: path)
@@ -164,13 +157,42 @@ module Legion
164
157
  at_exit do
165
158
  next if @state_mutex.synchronize { @active_leases.empty? }
166
159
 
167
- shutdown
160
+ Timeout.timeout(10) { shutdown }
161
+ rescue Timeout::Error
162
+ warn '[LeaseManager] at_exit shutdown timed out after 10s'
168
163
  rescue StandardError # best effort on crash
169
164
  nil
170
165
  end
171
166
  @at_exit_registered = true
172
167
  end
173
168
 
169
+ def cache_lease(name, response)
170
+ @state_mutex.synchronize do
171
+ @lease_cache[name] = response.data || {}
172
+ @active_leases[name] = {
173
+ lease_id: response.lease_id,
174
+ lease_duration: response.lease_duration,
175
+ renewable: response.renewable?,
176
+ expires_at: Time.now + (response.lease_duration || 0),
177
+ fetched_at: Time.now
178
+ }
179
+ end
180
+ end
181
+
182
+ def log_lease_response(name, response)
183
+ data_keys = response.data&.keys&.map(&:to_s) || []
184
+ log.debug("LeaseManager[#{name}]: lease_id=#{response.lease_id}, " \
185
+ "lease_duration=#{response.lease_duration}s, " \
186
+ "renewable=#{response.renewable?}, " \
187
+ "data_keys=#{data_keys.inspect}")
188
+ return unless response.data&.key?(:username)
189
+
190
+ log.debug("LeaseManager[#{name}]: username=#{response.data[:username]}, " \
191
+ "password_length=#{response.data[:password]&.length || 0}, " \
192
+ "vhost=#{response.data[:vhost] || 'N/A'}, " \
193
+ "tags=#{response.data[:tags] || 'N/A'}")
194
+ end
195
+
174
196
  def logical
175
197
  client = @state_mutex.synchronize { @vault_client }
176
198
  client ? client.logical : ::Vault.logical
@@ -72,7 +72,8 @@ module Legion
72
72
  service_principal: nil,
73
73
  auth_path: 'auth/kerberos/login'
74
74
  },
75
- clusters: {}
75
+ clusters: {},
76
+ bootstrap_lease_ttl: 300
76
77
  }
77
78
  end
78
79
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Crypt
5
- VERSION = '1.5.1'
5
+ VERSION = '1.5.3'
6
6
  end
7
7
  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.5.1
4
+ version: 1.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: concurrent-ruby
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.3'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: ed25519
14
28
  requirement: !ruby/object:Gem::Requirement