legion-crypt 1.5.5 → 1.5.7

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: 85275c53d61c6172fcabf74c69df85324c94d828372894e7d29e2c550501fc88
4
- data.tar.gz: 61a939f7fb79750ca314840c2c16766e7d325eb956e2d9e3d6196174bde1f0ea
3
+ metadata.gz: 3b695e8ac3853b730218da5e1b40b9f7bdc9c7fbffbd1ec4eddccd5539fe296a
4
+ data.tar.gz: 544bf702cdcc9114b7fa0b99c14141fa6f58b37ee9dbde767e953076ffaedb4c
5
5
  SHA512:
6
- metadata.gz: 3d7d33ed75be9d7263b32e9cb3830bff352dddd68fff8a8f060c26a7856a1962a8be31fa2955cabb76b6c961a7891890fae22b94c001e718b91ecdb5a2df3286
7
- data.tar.gz: c6df0c72440d9ac6cb134bbd1498d68f51a582431d974ee42a7084e9077170125047e0d55293623109c8c14562f34b86577482d100e2d82ad06125a09ade17d1
6
+ metadata.gz: dcf4c2dd40eeb403ec1708be60992abf7970d73e1f084f84cd840c4a1718c70497cee70a6bfe5b3d402852a9289f8498a3afbc23973a557e3f1cca355d2fefbe
7
+ data.tar.gz: a90c05a4b59d770f8cd0276008e9454e15e966e570ebf0248f8d5bb8fa48c67120c91d357e8b7225d14ec98547c41cc2914c130a65022e416e6739e489d4c526
data/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.5.7] - 2026-04-08
6
+
7
+ ### Fixed
8
+ - `LeaseManager#cache_lease` now stores the `:path` from static lease definitions, enabling `reissue_lease` fallback when `sys.renew` fails or leases hit max_ttl — previously static leases (configured via `crypt.vault.leases`) would silently expire after their TTL with no recovery (fixes #28)
9
+ - `LeaseManager#renew_lease` now logs a warning and falls back to reissue when the path is available, or warns explicitly when no path is available — previously renewal failures for pathless leases were silent
10
+
11
+ ### Added
12
+ - `LeaseManager#trigger_reconnect(name)` — dispatches reconnect to the appropriate service after credential reissue: `:rabbitmq` → `Transport::Connection.force_reconnect`, `:postgresql` → `Data.reconnect`, `:redis` → `Cache.reconnect`; all guarded with `defined?`/`respond_to?` and rescue-safe
13
+ - Comprehensive INFO/WARN logging across the entire lease lifecycle:
14
+ - INFO on lease fetch attempt, fetch success (with lease_id/ttl/renewable), renewal attempt, renewal success (with new_ttl), reissue attempt, reissue success (with new_lease_id/ttl), approaching expiry detection (with remaining/renewable/has_path), credentials changed during renewal, reconnect triggered, renewal loop start/exit
15
+ - WARN on non-renewable lease with no reissue path, renewal failure with no reissue path, reissue returning no data, reconnect failure, cannot reissue due to missing path
16
+
17
+ ### Changed
18
+ - `LeaseManager#reissue_lease` now calls `trigger_reconnect(name)` instead of inline `:rabbitmq`-only `force_reconnect`, extending credential rotation reconnect support to PostgreSQL and Redis
19
+
20
+ ## [1.5.6] - 2026-04-07
21
+
22
+ ### Added
23
+ - `VaultEntity` module (`lib/legion/crypt/vault_entity.rb`) — Phase 7 Vault identity tracking
24
+ - `ensure_entity(principal_id:, canonical_name:, metadata: {})` — creates or finds a Vault entity for a Legion principal; entity names are prefixed with `legion-` to avoid collision; metadata includes `legion_principal_id`, `legion_canonical_name`, and `managed_by: 'legion'`; returns entity ID string or nil on failure (non-fatal)
25
+ - `ensure_alias(entity_id:, mount_accessor:, alias_name:)` — creates an entity alias linking an auth method mount to the entity; idempotent (`already exists` HTTPClientError is swallowed); all other `Vault::HTTPClientError` responses log warn and return nil (non-fatal)
26
+ - `find_by_name(canonical_name)` — looks up a Vault entity by its Legion canonical name via `identity/entity/name/legion-{name}`; returns entity ID or nil
27
+ - All operations are non-fatal — rescue and log warn on failure; boot/request flow is never blocked by entity tracking errors
28
+ - Delegates Vault API calls to `LeaseManager.instance.vault_logical` (public delegator) when available; falls back to `::Vault.logical`
29
+
5
30
  ## [1.5.5] - 2026-04-07
6
31
 
7
32
  ### Added
data/CLAUDE.md CHANGED
@@ -8,7 +8,7 @@
8
8
  Handles encryption, decryption, secrets management, JWT token management, and HashiCorp Vault connectivity for the LegionIO framework. Provides AES-256-CBC message encryption, RSA key pair generation, cluster secret management, JWT issue/verify operations, and Vault token lifecycle management.
9
9
 
10
10
  **GitHub**: https://github.com/LegionIO/legion-crypt
11
- **Version**: 1.4.15
11
+ **Version**: 1.5.7
12
12
  **License**: Apache-2.0
13
13
 
14
14
  ## Architecture
@@ -63,6 +63,11 @@ Legion::Crypt (singleton module)
63
63
  ├── Tls # TLS settings (cert/key/CA/verify_peer/Vault PKI)
64
64
  ├── Mtls # mTLS cert issuance (Vault PKI) + CertRotation background thread (50% TTL renewal)
65
65
  ├── TokenRenewer # Background renewal thread: 75% TTL renew, Kerberos re-auth on failure, exponential backoff
66
+ ├── Spiffe # SPIFFE identity support: parse_id, valid_id?, X509Svid, JwtSvid structs; reads security.spiffe settings
67
+ ├── Spiffe::IdentityHelpers # Mixin for SPIFFE identity operations
68
+ ├── Spiffe::SvidRotation # Background SVID renewal at 50% TTL
69
+ ├── Spiffe::WorkloadApiClient # gRPC workload API client for SPIRE agent (unix socket)
70
+ ├── VaultEntity # Vault entity/alias lifecycle (ensure_entity, ensure_alias, find_by_name); all non-fatal
66
71
  ├── MockVault # In-memory Vault mock for local development mode
67
72
  ├── Settings # Default crypt config
68
73
  └── Version
@@ -132,6 +137,11 @@ Dev dependencies: `legion-logging`, `legion-settings`
132
137
  | `lib/legion/crypt/tls.rb` | TLS settings module (cert, key, CA paths, verify_peer, Vault PKI flag) |
133
138
  | `lib/legion/crypt/mtls.rb` | mTLS certificate issuance from Vault PKI; `CertRotation` background renewal thread (50% TTL) |
134
139
  | `lib/legion/crypt/token_renewer.rb` | Plain Thread renewer: renews at 75% TTL, re-auths via Kerberos on failure, exponential backoff |
140
+ | `lib/legion/crypt/spiffe.rb` | SPIFFE identity: parse/validate SPIFFE IDs, X509Svid/JwtSvid structs, settings helpers |
141
+ | `lib/legion/crypt/spiffe/identity_helpers.rb` | Mixin for SPIFFE identity operations |
142
+ | `lib/legion/crypt/spiffe/svid_rotation.rb` | Background SVID renewal thread (50% TTL) |
143
+ | `lib/legion/crypt/spiffe/workload_api_client.rb` | gRPC workload API client for SPIRE agent |
144
+ | `lib/legion/crypt/vault_entity.rb` | Vault entity/alias lifecycle: `ensure_entity`, `ensure_alias`, `find_by_name`; all operations non-fatal |
135
145
  | `lib/legion/crypt/version.rb` | VERSION constant |
136
146
 
137
147
  ## Role in LegionIO
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Encryption, secrets management, JWT token management, and HashiCorp Vault integration for the [LegionIO](https://github.com/LegionIO/LegionIO) framework. Provides AES-256-CBC message encryption, RSA key pair generation, cluster secret management, JWT issue/verify operations, Vault token lifecycle management, and multi-cluster Vault connectivity.
4
4
 
5
- **Version**: 1.4.22
5
+ **Version**: 1.5.6
6
6
 
7
7
  ## Installation
8
8
 
@@ -61,6 +61,7 @@ module Legion
61
61
  Thread.new do
62
62
  fetch_keys(jwks_url)
63
63
  rescue StandardError => e
64
+ handle_exception(e, level: :debug, operation: 'crypt.jwks.prefetch', jwks_url: jwks_url) if respond_to?(:handle_exception)
64
65
  log.debug "JWKS prefetch failed for #{jwks_url}: #{e.message}" if respond_to?(:log)
65
66
  end
66
67
  end
@@ -71,6 +72,7 @@ module Legion
71
72
  @refresh_task = Concurrent::TimerTask.new(execution_interval: interval, run_now: false) do
72
73
  fetch_keys(jwks_url)
73
74
  rescue StandardError => e
75
+ handle_exception(e, level: :debug, operation: 'crypt.jwks.background_refresh', jwks_url: jwks_url) if respond_to?(:handle_exception)
74
76
  log.debug "JWKS background refresh failed: #{e.message}" if respond_to?(:log)
75
77
  end
76
78
  @refresh_task.execute
@@ -121,7 +123,10 @@ module Legion
121
123
 
122
124
  response.body
123
125
  rescue StandardError => e
124
- raise Legion::Crypt::JWT::Error, "failed to fetch JWKS: #{e.message}" unless e.is_a?(Legion::Crypt::JWT::Error)
126
+ unless e.is_a?(Legion::Crypt::JWT::Error)
127
+ handle_exception(e, level: :warn, operation: 'crypt.jwks.http_get', url: url) if respond_to?(:handle_exception)
128
+ raise Legion::Crypt::JWT::Error, "failed to fetch JWKS: #{e.message}"
129
+ end
125
130
 
126
131
  raise
127
132
  end
@@ -40,6 +40,7 @@ module Legion
40
40
  revoke_expired_lease(name)
41
41
 
42
42
  begin
43
+ log.info("LeaseManager: fetching lease '#{name}' from #{path}")
43
44
  response = logical.read(path)
44
45
  unless response
45
46
  log.warn("LeaseManager: no data at '#{name}' (#{path}) — path may not exist or role not configured")
@@ -47,8 +48,9 @@ module Legion
47
48
  end
48
49
 
49
50
  log_lease_response(name, response)
50
- cache_lease(name, response)
51
- log.info("LeaseManager: fetched lease for '#{name}' from #{path}")
51
+ cache_lease(name, response, path: path)
52
+ log.info("LeaseManager: fetched lease '#{name}' from #{path} " \
53
+ "(lease_id=#{response.lease_id.to_s[0..11]}... ttl=#{response.lease_duration}s renewable=#{response.renewable?})")
52
54
  rescue StandardError => e
53
55
  handle_exception(e, level: :warn, operation: 'crypt.lease_manager.start', lease_name: name, path: path)
54
56
  log.warn("LeaseManager: failed to fetch lease '#{name}' from #{path}: #{e.message}")
@@ -129,14 +131,21 @@ module Legion
129
131
 
130
132
  def reissue_lease(name)
131
133
  lease = @state_mutex.synchronize { @active_leases[name]&.dup }
132
- return unless lease && lease[:path]
134
+ unless lease && lease[:path]
135
+ log.warn("LeaseManager: cannot reissue lease '#{name}' — no path stored for re-read")
136
+ return
137
+ end
133
138
 
139
+ log.info("LeaseManager: reissuing lease '#{name}' from #{lease[:path]}")
134
140
  response = logical.read(lease[:path])
135
- return unless response&.data
141
+ unless response&.data
142
+ log.warn("LeaseManager: reissue for '#{name}' returned no data from #{lease[:path]}")
143
+ return
144
+ end
136
145
 
137
- @state_mutex.synchronize do
146
+ updated = @state_mutex.synchronize do
138
147
  active_lease = @active_leases[name]
139
- next unless active_lease
148
+ next false unless active_lease
140
149
 
141
150
  @lease_cache[name] = response.data
142
151
  active_lease.merge!(
@@ -146,13 +155,18 @@ module Legion
146
155
  fetched_at: Time.now,
147
156
  renewable: response.renewable?
148
157
  )
158
+ true
159
+ end
160
+ unless updated
161
+ log.warn("LeaseManager: reissue for '#{name}' skipped — lease was removed during reissue (likely shutdown)")
162
+ return
149
163
  end
150
- push_to_settings(name)
151
-
152
- return unless name == :rabbitmq && defined?(Legion::Transport::Connection)
153
164
 
154
- Legion::Transport::Connection.force_reconnect
155
- log.info("LeaseManager: reissued lease '#{name}' and triggered transport reconnect")
165
+ lease_id_preview = response.lease_id.to_s[0..11]
166
+ log.info("LeaseManager: reissued lease '#{name}' " \
167
+ "(new_lease_id=#{lease_id_preview}... ttl=#{response.lease_duration}s)")
168
+ push_to_settings(name)
169
+ trigger_reconnect(name)
156
170
  rescue StandardError => e
157
171
  handle_exception(e, level: :warn, operation: 'crypt.lease_manager.reissue_lease', lease_name: name)
158
172
  log.warn("LeaseManager: failed to reissue lease '#{name}': #{e.message}")
@@ -227,7 +241,7 @@ module Legion
227
241
  @at_exit_registered = true
228
242
  end
229
243
 
230
- def cache_lease(name, response)
244
+ def cache_lease(name, response, path: nil)
231
245
  @state_mutex.synchronize do
232
246
  @lease_cache[name] = response.data || {}
233
247
  @active_leases[name] = {
@@ -235,7 +249,8 @@ module Legion
235
249
  lease_duration: response.lease_duration,
236
250
  renewable: response.renewable?,
237
251
  expires_at: Time.now + (response.lease_duration || 0),
238
- fetched_at: Time.now
252
+ fetched_at: Time.now,
253
+ path: path
239
254
  }
240
255
  end
241
256
  end
@@ -286,13 +301,15 @@ module Legion
286
301
  end
287
302
 
288
303
  def renewal_loop
304
+ log.info 'LeaseManager: renewal loop started'
289
305
  while running?
290
306
  interruptible_sleep(RENEWAL_CHECK_INTERVAL)
291
307
  renew_approaching_leases if running?
292
308
  end
309
+ log.info 'LeaseManager: renewal loop exiting'
293
310
  rescue StandardError => e
294
311
  handle_exception(e, level: :error, operation: 'crypt.lease_manager.renewal_loop')
295
- log.error("LeaseManager: renewal loop error: #{e.message}")
312
+ log.error("LeaseManager: renewal loop error: #{e.message} — restarting")
296
313
  retry if running?
297
314
  end
298
315
 
@@ -303,36 +320,71 @@ module Legion
303
320
  next unless lease
304
321
  next unless approaching_expiry?(lease)
305
322
 
323
+ remaining = lease[:expires_at] ? (lease[:expires_at] - Time.now).round(1) : 'unknown'
324
+ log.debug("LeaseManager: lease '#{name}' approaching expiry " \
325
+ "(remaining=#{remaining}s renewable=#{lease[:renewable]} has_path=#{!lease[:path].nil?})")
326
+
306
327
  if lease[:renewable]
307
328
  renew_lease(name, lease)
308
329
  elsif lease[:path]
309
- log.info("LeaseManager: lease '#{name}' is non-renewable and approaching expiry — re-issuing")
330
+ log.info("LeaseManager: lease '#{name}' is non-renewable — re-issuing from #{lease[:path]}")
310
331
  reissue_lease(name)
332
+ else
333
+ log.warn("LeaseManager: lease '#{name}' is non-renewable and has no path for reissue — " \
334
+ "will expire at #{lease[:expires_at]}")
311
335
  end
312
336
  end
313
337
  end
314
338
 
315
339
  def renew_lease(name, lease)
316
- response = sys.renew(lease[:lease_id])
340
+ lease_id = lease[:lease_id].to_s
341
+ if lease_id.empty?
342
+ log.warn("LeaseManager: lease '#{name}' is renewable but has no lease_id")
343
+ if lease[:path]
344
+ log.warn("LeaseManager: falling back to reissue for '#{name}' from #{lease[:path]}")
345
+ reissue_lease(name)
346
+ else
347
+ log.warn("LeaseManager: lease '#{name}' renewal failed and no path available for reissue — " \
348
+ "lease will expire at #{lease[:expires_at]}")
349
+ end
350
+ return
351
+ end
352
+
353
+ log.info("LeaseManager: renewing lease '#{name}' (lease_id=#{lease_id[0..11]}...)")
354
+ response = sys.renew(lease_id)
355
+ new_ttl = response.respond_to?(:lease_duration) ? response.lease_duration : nil
317
356
  @state_mutex.synchronize do
318
357
  current_lease = @active_leases[name]
319
358
  next unless current_lease
320
359
 
321
- current_lease[:lease_duration] = response.lease_duration if response.respond_to?(:lease_duration)
360
+ if new_ttl
361
+ current_lease[:lease_duration] = new_ttl
362
+ current_lease[:expires_at] = Time.now + new_ttl
363
+ end
322
364
  current_lease[:renewable] = response.renewable? if response.respond_to?(:renewable?)
323
- current_lease[:expires_at] = Time.now + (response.lease_duration || 0)
324
365
  end
325
- log.info("LeaseManager: renewed lease '#{name}'")
366
+ if new_ttl
367
+ log.info("LeaseManager: renewed lease '#{name}' (new_ttl=#{new_ttl}s)")
368
+ else
369
+ log.warn("LeaseManager: renewed lease '#{name}' but Vault returned no lease_duration — keeping previous TTL")
370
+ end
326
371
 
327
372
  cached_data = @state_mutex.synchronize { @lease_cache[name] }
328
373
  if response.data && response.data != cached_data
329
374
  @state_mutex.synchronize { @lease_cache[name] = response.data }
330
375
  push_to_settings(name)
376
+ log.info("LeaseManager: lease '#{name}' credentials changed during renewal — settings updated")
331
377
  end
332
378
  rescue StandardError => e
333
379
  handle_exception(e, level: :warn, operation: 'crypt.lease_manager.renew_lease', lease_name: name)
334
380
  log.warn("LeaseManager: failed to renew lease '#{name}': #{e.message}")
335
- reissue_lease(name) if lease[:path]
381
+ if lease[:path]
382
+ log.warn("LeaseManager: falling back to reissue for '#{name}' from #{lease[:path]}")
383
+ reissue_lease(name)
384
+ else
385
+ log.warn("LeaseManager: lease '#{name}' renewal failed and no path available for reissue — " \
386
+ "lease will expire at #{lease[:expires_at]}")
387
+ end
336
388
  end
337
389
 
338
390
  def lease_valid?(name)
@@ -390,6 +442,30 @@ module Legion
390
442
  log.warn("LeaseManager: failed to write setting at #{path.join('.')}: #{e.message}")
391
443
  end
392
444
 
445
+ def trigger_reconnect(name)
446
+ name = name.to_sym if name.respond_to?(:to_sym)
447
+ case name
448
+ when :rabbitmq
449
+ return unless defined?(Legion::Transport::Connection)
450
+
451
+ Legion::Transport::Connection.force_reconnect
452
+ log.info("LeaseManager: triggered transport reconnect after '#{name}' reissue")
453
+ when :postgresql
454
+ return unless defined?(Legion::Data) && Legion::Data.respond_to?(:reconnect)
455
+
456
+ Legion::Data.reconnect
457
+ log.info("LeaseManager: triggered data reconnect after '#{name}' reissue")
458
+ when :redis
459
+ return unless defined?(Legion::Cache) && Legion::Cache.respond_to?(:reconnect)
460
+
461
+ Legion::Cache.reconnect
462
+ log.info("LeaseManager: triggered cache reconnect after '#{name}' reissue")
463
+ end
464
+ rescue StandardError => e
465
+ handle_exception(e, level: :warn, operation: 'crypt.lease_manager.trigger_reconnect', lease_name: name)
466
+ log.warn("LeaseManager: reconnect for '#{name}' failed: #{e.message}")
467
+ end
468
+
393
469
  def running?
394
470
  @state_mutex.synchronize { @running }
395
471
  end
@@ -81,6 +81,7 @@ module Legion
81
81
  rescue ::Vault::HTTPError => e
82
82
  return true if e.message =~ /\b(429|472|473)\b/
83
83
 
84
+ handle_exception(e, level: :warn, operation: 'crypt.vault_cluster.cluster_healthy')
84
85
  raise
85
86
  end
86
87
 
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/logging/helper'
4
+
5
+ module Legion
6
+ module Crypt
7
+ module VaultEntity
8
+ extend Legion::Logging::Helper
9
+
10
+ # Create or lookup a Vault entity for a Legion principal.
11
+ # Returns the Vault entity ID string, or nil on failure.
12
+ def self.ensure_entity(principal_id:, canonical_name:, metadata: {})
13
+ existing = find_by_name(canonical_name)
14
+ return existing if existing
15
+
16
+ response = vault_logical.write(
17
+ 'identity/entity',
18
+ name: "legion-#{canonical_name}",
19
+ metadata: metadata.merge(
20
+ legion_principal_id: principal_id,
21
+ legion_canonical_name: canonical_name,
22
+ managed_by: 'legion'
23
+ )
24
+ )
25
+ extract_id(response)
26
+ rescue StandardError => e
27
+ handle_exception(e, level: :warn, operation: 'crypt.vault_entity.ensure_entity', canonical_name: canonical_name)
28
+ nil
29
+ end
30
+
31
+ # Create an alias linking an auth method mount to the entity.
32
+ # Idempotent — swallows "already exists" 4xx errors.
33
+ def self.ensure_alias(entity_id:, mount_accessor:, alias_name:)
34
+ vault_logical.write(
35
+ 'identity/entity-alias',
36
+ name: alias_name,
37
+ canonical_id: entity_id,
38
+ mount_accessor: mount_accessor
39
+ )
40
+ rescue ::Vault::HTTPClientError => e
41
+ if e.message.include?('already exists')
42
+ log.debug 'Vault entity alias already exists (idempotent)'
43
+ else
44
+ handle_exception(e, level: :warn, operation: 'crypt.vault_entity.ensure_alias', alias_name: alias_name)
45
+ end
46
+ nil
47
+ rescue StandardError => e
48
+ handle_exception(e, level: :warn, operation: 'crypt.vault_entity.ensure_alias', alias_name: alias_name)
49
+ nil
50
+ end
51
+
52
+ # Look up a Vault entity by its Legion canonical name.
53
+ # Returns the Vault entity ID string, or nil if not found.
54
+ def self.find_by_name(canonical_name)
55
+ response = vault_logical.read("identity/entity/name/legion-#{canonical_name}")
56
+ extract_id(response)
57
+ rescue ::Vault::HTTPClientError => e
58
+ unless e.message.match?(/not found|does not exist|404/i)
59
+ handle_exception(e, level: :warn, operation: 'crypt.vault_entity.find_by_name', canonical_name: canonical_name)
60
+ end
61
+ nil
62
+ rescue StandardError => e
63
+ handle_exception(e, level: :warn, operation: 'crypt.vault_entity.find_by_name', canonical_name: canonical_name)
64
+ nil
65
+ end
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # Private helpers
69
+ # ---------------------------------------------------------------------------
70
+
71
+ def self.vault_logical
72
+ if defined?(Legion::Crypt::LeaseManager)
73
+ Legion::Crypt::LeaseManager.instance.vault_logical
74
+ else
75
+ ::Vault.logical
76
+ end
77
+ end
78
+ private_class_method :vault_logical
79
+
80
+ # Extract entity ID from a Vault response, supporting both symbol and
81
+ # string keys (Vault SDK may return either depending on version/transport).
82
+ def self.extract_id(response)
83
+ data = response&.data
84
+ return nil unless data
85
+
86
+ data[:id] || data['id']
87
+ end
88
+ private_class_method :extract_id
89
+ end
90
+ end
91
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Crypt
5
- VERSION = '1.5.5'
5
+ VERSION = '1.5.7'
6
6
  end
7
7
  end
data/lib/legion/crypt.rb CHANGED
@@ -138,6 +138,7 @@ module Legion
138
138
 
139
139
  log.info "Bootstrap RMQ credentials acquired (lease: #{@bootstrap_lease_id[0..7]}...)"
140
140
  rescue StandardError => e
141
+ handle_exception(e, level: :warn, operation: 'crypt.fetch_bootstrap_rmq_creds')
141
142
  log.warn "Bootstrap RMQ credential fetch failed: #{e.message}"
142
143
  end
143
144
 
@@ -188,6 +189,7 @@ module Legion
188
189
  @bootstrap_lease_id = nil
189
190
  @bootstrap_lease_expires = nil
190
191
  rescue StandardError => e
192
+ handle_exception(e, level: :warn, operation: 'crypt.revoke_bootstrap_lease')
191
193
  log.warn "Bootstrap lease revocation failed: #{e.message} — lease will expire naturally"
192
194
  @bootstrap_lease_id = nil
193
195
  @bootstrap_lease_expires = nil
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.5
4
+ version: 1.5.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -127,6 +127,7 @@ files:
127
127
  - lib/legion/crypt/token_renewer.rb
128
128
  - lib/legion/crypt/vault.rb
129
129
  - lib/legion/crypt/vault_cluster.rb
130
+ - lib/legion/crypt/vault_entity.rb
130
131
  - lib/legion/crypt/vault_jwt_auth.rb
131
132
  - lib/legion/crypt/vault_kerberos_auth.rb
132
133
  - lib/legion/crypt/vault_renewer.rb