legion-crypt 1.5.7 → 1.5.9
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 +23 -0
- data/lib/legion/crypt/jwks_client.rb +11 -1
- data/lib/legion/crypt/lease_manager.rb +26 -6
- data/lib/legion/crypt/settings.rb +5 -1
- data/lib/legion/crypt/token_renewer.rb +24 -5
- data/lib/legion/crypt/vault.rb +9 -1
- data/lib/legion/crypt/vault_cluster.rb +12 -3
- data/lib/legion/crypt/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 42200f56d577b407725621c56af4f7d191c1ad0219c33c99f5e4adc02568aac6
|
|
4
|
+
data.tar.gz: c231674d541726ab2fa3cf381e655a67be065e2481ce4f50c39d42b52875f6fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ee34bea6a9a73af47707259d8b857dfc1c1eb8f9e3519ccbefd811bb43e182c734795d15732de82058acb1101f3ba14bc16c39feeae87873c290e2aad58c049
|
|
7
|
+
data.tar.gz: b4d4c72a242b0cfb229dcb27f654f41e6dfd6c3035c73901345588639ea102969e83edb31e5b9572705f674eeba8bd3a6fe89915b9db4ca2eedd33749c12fcf3
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.5.9] - 2026-04-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Vault lease cascade revocation: all three service credentials (RabbitMQ, PostgreSQL, Redis) died at exactly 2 hours when the Vault Kerberos auth token expired — Vault cascade-revokes all child leases when the parent token dies, regardless of individual lease TTLs (closes #29)
|
|
9
|
+
- `TokenRenewer` now detects non-renewable tokens (`renewable=false`) and skips `renew_self` (which always fails for non-renewable tokens), going straight to `reauth_kerberos` before the token expires
|
|
10
|
+
- `TokenRenewer#reauth_kerberos` now triggers `LeaseManager.reissue_all` after obtaining a new token, re-issuing all active leases under the new token so they are not orphaned when the old token expires
|
|
11
|
+
- `LeaseManager#push_to_settings` symbol/string key mismatch: `resolve_secrets!` registers refs with string keys (`"rabbitmq"`) via `lease://` URI parsing, but `cache_lease` stores leases with symbol keys (`:rabbitmq` from `Legion::JSON.load`) — now tries both key types
|
|
12
|
+
- `LeaseManager#trigger_reconnect` for `:postgresql` — uses surgical Sequel pool `disconnect` + `test_connection` instead of `Data.shutdown + Data.setup` which tore down unrelated connections (Apollo SQLite, Local cache)
|
|
13
|
+
- `LeaseManager#trigger_reconnect` for `:redis` — uses `Cache.restart` (the actual method) instead of `Cache.reconnect` (which does not exist)
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- `LeaseManager#reissue_all` — re-issues all active leases under the current vault client token; called by `TokenRenewer` after successful Kerberos re-authentication to prevent cascade revocation of orphaned leases
|
|
17
|
+
|
|
18
|
+
## [1.5.8] - 2026-04-09
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Configurable SSL verification for Vault connections via `crypt.vault.tls.verify` setting (`peer`/`none`/`mutual`, defaults to `peer`)
|
|
22
|
+
- Global Vault client (`vault.rb`) now sets `::Vault.ssl_verify` from `vault.tls.verify` setting
|
|
23
|
+
- Per-cluster Vault clients (`vault_cluster.rb`) now pass `ssl_verify:` to `::Vault::Client.new` from `config[:tls][:verify]`
|
|
24
|
+
- JWKS client (`jwks_client.rb`) now sets `Net::HTTP#verify_mode` from `crypt.jwt.jwks_tls_verify` setting (`peer`/`none`, defaults to `peer`)
|
|
25
|
+
- `jwks_tls_verify: 'peer'` default added to JWT settings
|
|
26
|
+
- `tls: { verify: 'peer' }` default added to Vault settings
|
|
27
|
+
|
|
5
28
|
## [1.5.7] - 2026-04-08
|
|
6
29
|
|
|
7
30
|
### Fixed
|
|
@@ -112,7 +112,8 @@ module Legion
|
|
|
112
112
|
raise Legion::Crypt::JWT::Error, 'failed to fetch JWKS: HTTPS is required' unless uri.scheme == 'https'
|
|
113
113
|
|
|
114
114
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
115
|
-
http.use_ssl =
|
|
115
|
+
http.use_ssl = true
|
|
116
|
+
http.verify_mode = jwks_ssl_verify_mode
|
|
116
117
|
http.open_timeout = 10
|
|
117
118
|
http.read_timeout = 10
|
|
118
119
|
|
|
@@ -158,6 +159,15 @@ module Legion
|
|
|
158
159
|
keys
|
|
159
160
|
end
|
|
160
161
|
|
|
162
|
+
def jwks_ssl_verify_mode
|
|
163
|
+
return OpenSSL::SSL::VERIFY_PEER unless defined?(Legion::Settings)
|
|
164
|
+
|
|
165
|
+
verify = Legion::Settings[:crypt][:jwt][:jwks_tls_verify]&.to_s
|
|
166
|
+
verify == 'none' ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
|
167
|
+
rescue StandardError
|
|
168
|
+
OpenSSL::SSL::VERIFY_PEER
|
|
169
|
+
end
|
|
170
|
+
|
|
161
171
|
def with_url_lock(jwks_url, &)
|
|
162
172
|
lock = @locks_mutex.synchronize { @locks[jwks_url] ||= Mutex.new }
|
|
163
173
|
lock.synchronize(&)
|
|
@@ -86,7 +86,9 @@ module Legion
|
|
|
86
86
|
|
|
87
87
|
def push_to_settings(name)
|
|
88
88
|
refs, data = @state_mutex.synchronize do
|
|
89
|
-
[@refs[name]
|
|
89
|
+
r = @refs[name] || @refs[name.to_s] || @refs[name.to_sym]
|
|
90
|
+
d = @lease_cache[name] || @lease_cache[name.to_s] || @lease_cache[name.to_sym]
|
|
91
|
+
[r&.dup, d&.dup]
|
|
90
92
|
end
|
|
91
93
|
return if refs.nil? || refs.empty?
|
|
92
94
|
return unless data
|
|
@@ -109,6 +111,19 @@ module Legion
|
|
|
109
111
|
sys
|
|
110
112
|
end
|
|
111
113
|
|
|
114
|
+
def reissue_all
|
|
115
|
+
log.info('LeaseManager: reissue_all — re-issuing all active leases under new token')
|
|
116
|
+
lease_names = @state_mutex.synchronize { @active_leases.keys.dup }
|
|
117
|
+
|
|
118
|
+
lease_names.each do |name|
|
|
119
|
+
lease = @state_mutex.synchronize { @active_leases[name]&.dup }
|
|
120
|
+
next unless lease && lease[:path]
|
|
121
|
+
|
|
122
|
+
reissue_lease(name)
|
|
123
|
+
end
|
|
124
|
+
log.info('LeaseManager: reissue_all complete')
|
|
125
|
+
end
|
|
126
|
+
|
|
112
127
|
def register_dynamic_lease(name:, path:, response:, settings_refs:)
|
|
113
128
|
register_at_exit_hook
|
|
114
129
|
|
|
@@ -451,14 +466,19 @@ module Legion
|
|
|
451
466
|
Legion::Transport::Connection.force_reconnect
|
|
452
467
|
log.info("LeaseManager: triggered transport reconnect after '#{name}' reissue")
|
|
453
468
|
when :postgresql
|
|
454
|
-
return unless defined?(Legion::Data) && Legion::Data.
|
|
469
|
+
return unless defined?(Legion::Data::Connection) && Legion::Data::Connection.sequel
|
|
455
470
|
|
|
456
|
-
Legion::Data.
|
|
457
|
-
|
|
471
|
+
Legion::Data::Connection.sequel.disconnect
|
|
472
|
+
Legion::Data::Connection.sequel.test_connection
|
|
473
|
+
log.info("LeaseManager: triggered data pool reconnect after '#{name}' reissue")
|
|
458
474
|
when :redis
|
|
459
|
-
return unless defined?(Legion::Cache)
|
|
475
|
+
return unless defined?(Legion::Cache)
|
|
460
476
|
|
|
461
|
-
Legion::Cache.
|
|
477
|
+
if Legion::Cache.respond_to?(:restart)
|
|
478
|
+
Legion::Cache.restart
|
|
479
|
+
elsif Legion::Cache.respond_to?(:reconnect)
|
|
480
|
+
Legion::Cache.reconnect
|
|
481
|
+
end
|
|
462
482
|
log.info("LeaseManager: triggered cache reconnect after '#{name}' reissue")
|
|
463
483
|
end
|
|
464
484
|
rescue StandardError => e
|
|
@@ -48,7 +48,8 @@ module Legion
|
|
|
48
48
|
default_ttl: 3600,
|
|
49
49
|
issuer: 'legion',
|
|
50
50
|
verify_expiration: true,
|
|
51
|
-
verify_issuer: true
|
|
51
|
+
verify_issuer: true,
|
|
52
|
+
jwks_tls_verify: 'peer'
|
|
52
53
|
}
|
|
53
54
|
end
|
|
54
55
|
|
|
@@ -72,6 +73,9 @@ module Legion
|
|
|
72
73
|
service_principal: nil,
|
|
73
74
|
auth_path: 'auth/kerberos/login'
|
|
74
75
|
},
|
|
76
|
+
tls: {
|
|
77
|
+
verify: 'peer'
|
|
78
|
+
},
|
|
75
79
|
clusters: {},
|
|
76
80
|
bootstrap_lease_ttl: 300,
|
|
77
81
|
dynamic_rmq_creds: false,
|
|
@@ -72,7 +72,9 @@ module Legion
|
|
|
72
72
|
@config[:renewable] = result[:renewable]
|
|
73
73
|
@config[:connected] = true
|
|
74
74
|
@vault_client.token = result[:token]
|
|
75
|
-
log.info("TokenRenewer[#{@cluster_name}]: re-authenticated via Kerberos")
|
|
75
|
+
log.info("TokenRenewer[#{@cluster_name}]: re-authenticated via Kerberos, ttl=#{result[:lease_duration]}s")
|
|
76
|
+
|
|
77
|
+
reissue_all_leases
|
|
76
78
|
true
|
|
77
79
|
rescue StandardError => e
|
|
78
80
|
handle_exception(e, level: :warn, operation: 'crypt.token_renewer.reauth_kerberos', cluster_name: @cluster_name)
|
|
@@ -104,10 +106,18 @@ module Legion
|
|
|
104
106
|
interruptible_sleep(sleep_duration)
|
|
105
107
|
|
|
106
108
|
until @stop
|
|
107
|
-
if
|
|
108
|
-
|
|
109
|
+
if @config[:renewable]
|
|
110
|
+
if renew_token || reauth_kerberos
|
|
111
|
+
on_renewal_success
|
|
112
|
+
else
|
|
113
|
+
on_renewal_failure
|
|
114
|
+
end
|
|
109
115
|
else
|
|
110
|
-
|
|
116
|
+
if reauth_kerberos # rubocop:disable Style/IfInsideElse
|
|
117
|
+
on_renewal_success
|
|
118
|
+
else
|
|
119
|
+
on_renewal_failure
|
|
120
|
+
end
|
|
111
121
|
end
|
|
112
122
|
end
|
|
113
123
|
rescue StandardError => e
|
|
@@ -128,6 +138,15 @@ module Legion
|
|
|
128
138
|
interruptible_sleep(delay)
|
|
129
139
|
end
|
|
130
140
|
|
|
141
|
+
def reissue_all_leases
|
|
142
|
+
return unless defined?(Legion::Crypt::LeaseManager)
|
|
143
|
+
|
|
144
|
+
Legion::Crypt::LeaseManager.instance.reissue_all
|
|
145
|
+
rescue StandardError => e
|
|
146
|
+
handle_exception(e, level: :warn, operation: 'crypt.token_renewer.reissue_all_leases', cluster_name: @cluster_name)
|
|
147
|
+
log.warn("TokenRenewer[#{@cluster_name}]: failed to reissue leases after reauth: #{e.message}")
|
|
148
|
+
end
|
|
149
|
+
|
|
131
150
|
def interruptible_sleep(seconds)
|
|
132
151
|
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + seconds
|
|
133
152
|
loop do
|
|
@@ -150,7 +169,7 @@ module Legion
|
|
|
150
169
|
else
|
|
151
170
|
@thread = nil
|
|
152
171
|
revoke_token
|
|
153
|
-
log.
|
|
172
|
+
log.info("TokenRenewer[#{@cluster_name}]: token renewal thread stopped")
|
|
154
173
|
end
|
|
155
174
|
end
|
|
156
175
|
|
data/lib/legion/crypt/vault.rb
CHANGED
|
@@ -19,8 +19,9 @@ module Legion
|
|
|
19
19
|
@sessions = []
|
|
20
20
|
vault_settings = Legion::Settings[:crypt][:vault]
|
|
21
21
|
::Vault.address = resolve_vault_address(vault_settings)
|
|
22
|
+
::Vault.ssl_verify = resolve_ssl_verify(vault_settings[:tls])
|
|
22
23
|
namespace = vault_settings[:vault_namespace]
|
|
23
|
-
log.info "Vault connection requested address=#{::Vault.address} namespace=#{namespace || 'none'}"
|
|
24
|
+
log.info "Vault connection requested address=#{::Vault.address} namespace=#{namespace || 'none'} ssl_verify=#{::Vault.ssl_verify}"
|
|
24
25
|
|
|
25
26
|
Legion::Settings[:crypt][:vault][:token] = ENV['VAULT_DEV_ROOT_TOKEN_ID'] if ENV.key? 'VAULT_DEV_ROOT_TOKEN_ID'
|
|
26
27
|
return nil if Legion::Settings[:crypt][:vault][:token].nil?
|
|
@@ -209,6 +210,13 @@ module Legion
|
|
|
209
210
|
data[:data]
|
|
210
211
|
end
|
|
211
212
|
|
|
213
|
+
def resolve_ssl_verify(tls_config)
|
|
214
|
+
return true if tls_config.nil?
|
|
215
|
+
|
|
216
|
+
verify = tls_config[:verify]&.to_s
|
|
217
|
+
verify != 'none'
|
|
218
|
+
end
|
|
219
|
+
|
|
212
220
|
def resolve_vault_address(vault_settings)
|
|
213
221
|
protocol = vault_settings[:protocol] || 'http'
|
|
214
222
|
address = vault_settings[:address] || 'localhost'
|
|
@@ -118,11 +118,13 @@ module Legion
|
|
|
118
118
|
return nil unless config.is_a?(Hash)
|
|
119
119
|
|
|
120
120
|
addr = "#{config[:protocol]}://#{config[:address]}:#{config[:port]}"
|
|
121
|
-
|
|
121
|
+
ssl_verify = resolve_cluster_ssl_verify(config[:tls])
|
|
122
|
+
log.info "Building Vault client address=#{addr} namespace=#{config[:namespace].inspect} ssl_verify=#{ssl_verify}"
|
|
122
123
|
log_vault_debug("build_vault_client: address=#{addr}")
|
|
123
124
|
client = ::Vault::Client.new(
|
|
124
|
-
address:
|
|
125
|
-
token:
|
|
125
|
+
address: addr,
|
|
126
|
+
token: config[:token],
|
|
127
|
+
ssl_verify: ssl_verify
|
|
126
128
|
)
|
|
127
129
|
namespace =
|
|
128
130
|
if config.key?(:namespace)
|
|
@@ -136,6 +138,13 @@ module Legion
|
|
|
136
138
|
client
|
|
137
139
|
end
|
|
138
140
|
|
|
141
|
+
def resolve_cluster_ssl_verify(tls_config)
|
|
142
|
+
return true if tls_config.nil?
|
|
143
|
+
|
|
144
|
+
verify = tls_config[:verify]&.to_s
|
|
145
|
+
verify != 'none'
|
|
146
|
+
end
|
|
147
|
+
|
|
139
148
|
def log_vault_error(name, error, operation: 'crypt.vault_cluster.error')
|
|
140
149
|
handle_exception(error, level: :error, operation: operation, cluster_name: name)
|
|
141
150
|
end
|
data/lib/legion/crypt/version.rb
CHANGED