legion-data 1.10.7 → 1.10.8

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: bc598e354968c28944d6e897feb5853d57987fed49eaf0cd16f319abcfbd8cf8
4
- data.tar.gz: ebc17c4d4b8c49585bcdb0a49803c4988e8d55970ddde56b294746d810193f63
3
+ metadata.gz: 869153710ece6717285edf2de7483f4c9f5fe5b493837681b80d05b77b014971
4
+ data.tar.gz: 2813fd707f0f7a80858802046b7ba2a2f23611f94a1515c66e5c7a5db1771dd6
5
5
  SHA512:
6
- metadata.gz: 7f36631ecd03c493fa2dfd3b347a4f16765b7900545d31306e4b73f0418a455a99c15ab55a8de83ad86c123befede09a222a67a6095f624923028c585ca9c7d0
7
- data.tar.gz: 9a1783c655df6dce9c23a4a878645cf196e4463d0763d95c380029263a8deeccce7cfce8e66ede4843a04c68e8369232d8d4b785460bd70181d4ad1adcf1c01b
6
+ metadata.gz: 41aff7bbce675a95d8a2ec6683eb0350decde2cf66854971b0c43fed1d4a09bdc50563e922bfe29d5a01660c522d579be7f2719495150d136ba262f433ba4ac4
7
+ data.tar.gz: c766be4f0a13cf3ac3dc91e305d275742d5c49206ec90819631d05739137fb1c9eadbc550feeef1d6e635961ec8bf439b9b5fc19dfc334d1798859573d87902a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Legion::Data Changelog
2
2
 
3
+ ## [1.10.8] - 2026-07-23
4
+ ### Fixed
5
+ - Detect auth failures (`role does not exist`, `password authentication failed`) on Sequel pool connections and trigger immediate Vault lease reissue via LeaseManager instead of retrying dead credentials forever. 30-second cooldown prevents reissue storms during bulk credential rotation.
6
+
3
7
  ## [1.10.7] - 2026-07-15
4
8
  ### Fixed
5
9
  - Fail loud with actionable error when unresolved lease:// credentials reach connection
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/logging/helper'
4
+
5
+ module Legion
6
+ module Data
7
+ module AuthFailureHandler
8
+ extend Legion::Logging::Helper
9
+
10
+ AUTH_FAILURE_PATTERNS = [
11
+ /role .* does not exist/i,
12
+ /password authentication failed/i,
13
+ /authentication failed/i,
14
+ /no pg_hba\.conf entry/i,
15
+ /permission denied for table/i,
16
+ /permission denied for relation/i
17
+ ].freeze
18
+
19
+ REISSUE_COOLDOWN = 30
20
+
21
+ @last_reissue_at = nil
22
+ @mutex = Mutex.new
23
+
24
+ module SequelHook
25
+ def connect(server)
26
+ super
27
+ rescue StandardError => e
28
+ Legion::Data::AuthFailureHandler.handle(e)
29
+ raise
30
+ end
31
+
32
+ def raise_error(exception, opts = Sequel::OPTS)
33
+ Legion::Data::AuthFailureHandler.handle(exception)
34
+ super
35
+ end
36
+ end
37
+
38
+ class << self
39
+ def install(sequel_db)
40
+ sequel_db.singleton_class.prepend(SequelHook)
41
+ end
42
+
43
+ def handle(error)
44
+ return unless auth_failure?(error)
45
+ return if on_cooldown?
46
+
47
+ request_reissue(error)
48
+ end
49
+
50
+ def auth_failure?(error)
51
+ message = error.message.to_s
52
+ AUTH_FAILURE_PATTERNS.any? { |pattern| message.match?(pattern) }
53
+ end
54
+
55
+ def on_cooldown?
56
+ @mutex.synchronize do
57
+ return false unless @last_reissue_at
58
+
59
+ (Time.now - @last_reissue_at) < REISSUE_COOLDOWN
60
+ end
61
+ end
62
+
63
+ def request_reissue(error, adapter: :postgresql)
64
+ @mutex.synchronize { @last_reissue_at = Time.now }
65
+ log.error("Legion::Data auth failure detected: #{error.message} — requesting lease reissue")
66
+
67
+ return unless defined?(Legion::Crypt::LeaseManager)
68
+
69
+ Legion::Crypt::LeaseManager.instance.reissue_lease(adapter)
70
+ rescue StandardError => e
71
+ handle_exception(e, level: :error, handled: true, operation: :auth_failure_reissue)
72
+ end
73
+
74
+ def reset!
75
+ @mutex.synchronize { @last_reissue_at = nil }
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/logging/helper'
4
+ require 'legion/data/auth_failure_handler'
4
5
 
5
6
  require 'fileutils'
6
7
  require 'sequel'
@@ -299,6 +300,7 @@ module Legion
299
300
  @sequel.opts[:password] = new_pass
300
301
 
301
302
  @sequel.disconnect
303
+ expire_all_pooled_connections
302
304
 
303
305
  @sequel.test_connection
304
306
  log.info("reconnect_with_fresh_creds: rotated credentials (#{old_user} → #{new_user})")
@@ -309,6 +311,16 @@ module Legion
309
311
  false
310
312
  end
311
313
 
314
+ def expire_all_pooled_connections
315
+ pool = @sequel.pool
316
+ return unless pool.instance_variable_defined?(:@connection_expiration_timestamps)
317
+
318
+ timestamps = pool.instance_variable_get(:@connection_expiration_timestamps)
319
+ timestamps.each_key { |conn| timestamps[conn] = [0, 0].freeze }
320
+ rescue StandardError => e
321
+ handle_exception(e, level: :warn, handled: true, operation: :expire_all_pooled_connections)
322
+ end
323
+
312
324
  def connect_with_replicas
313
325
  return unless adapter == :postgres
314
326
 
@@ -397,10 +409,22 @@ module Legion
397
409
  conn_host = actual[:host] || '127.0.0.1'
398
410
  conn_port = actual[:port]
399
411
  conn_db = actual[:database] || actual[:db]
400
- log.info "Connected to #{adapter}://#{conn_user}@#{conn_host}:#{conn_port}/#{conn_db}"
412
+ lease_id = current_lease_id
413
+ msg = "Connected to #{adapter}://#{conn_user}@#{conn_host}:#{conn_port}/#{conn_db}"
414
+ msg += " lease_id=#{lease_id}" if lease_id
415
+ log.info msg
401
416
  end
402
417
  end
403
418
 
419
+ def current_lease_id
420
+ return unless defined?(Legion::Crypt::LeaseManager)
421
+
422
+ Legion::Crypt::LeaseManager.instance.active_leases.dig(:postgresql, :lease_id)
423
+ rescue StandardError => e
424
+ handle_exception(e, level: :warn, handled: true, operation: :current_lease_id)
425
+ nil
426
+ end
427
+
404
428
  def dev_fallback?
405
429
  data_settings = Legion::Settings[:data]
406
430
  data_settings[:dev_mode] == true && data_settings[:dev_fallback] != false
@@ -608,10 +632,18 @@ module Legion
608
632
  @sequel.extension(:connection_expiration)
609
633
  @sequel.pool.connection_expiration_timeout = data[:connection_expiration_timeout]
610
634
  end
635
+
636
+ install_auth_failure_hook
611
637
  rescue StandardError => e
612
638
  handle_exception(e, level: :warn, handled: true, operation: :configure_extensions, adapter: adapter)
613
639
  end
614
640
 
641
+ def install_auth_failure_hook
642
+ Legion::Data::AuthFailureHandler.install(@sequel)
643
+ rescue StandardError => e
644
+ handle_exception(e, level: :warn, handled: true, operation: :install_auth_failure_hook)
645
+ end
646
+
615
647
  def build_data_logger
616
648
  tagged = if defined?(Legion::Logging::TaggedLogger) && respond_to?(:tagged_logger_settings, true)
617
649
  Legion::Logging::TaggedLogger.new(
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Data
5
- VERSION = '1.10.7'
5
+ VERSION = '1.10.8'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-data
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.7
4
+ version: 1.10.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -124,6 +124,7 @@ files:
124
124
  - lib/legion/data/archiver.rb
125
125
  - lib/legion/data/audit_log_hash_chain.rb
126
126
  - lib/legion/data/audit_record.rb
127
+ - lib/legion/data/auth_failure_handler.rb
127
128
  - lib/legion/data/connection.rb
128
129
  - lib/legion/data/encryption/cipher.rb
129
130
  - lib/legion/data/encryption/key_provider.rb