legion-crypt 1.5.0 → 1.5.2
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/AGENTS.md +9 -0
- data/CHANGELOG.md +11 -0
- data/lib/legion/crypt/lease_manager.rb +47 -10
- data/lib/legion/crypt/vault.rb +1 -1
- 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: 01cec7ac57c7d5448b5fba4237fdccc9ac8099966a4a546e95dd4888d838604f
|
|
4
|
+
data.tar.gz: ac81d602c9fc34493aeabe3fdeadd12f01fb36f8c248ac44687166fd24d9a48d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dea3247dbaf00a49507ededcfdd72e9e3545485cbcec511657d2e2c602e03f2230c3c34e93108ae14ede71aa7a768a36573c3f852595444f2f6ed118a351bd8c
|
|
7
|
+
data.tar.gz: 011ac0f235a69f7655ab1237d2a87faf3d343de20ef1ffe1e9576b5a555e9c0e5d46dfc6d77dd137851835850e3877783c70f62a216c3dd1a4af9c3e080e9ed6
|
data/AGENTS.md
CHANGED
|
@@ -32,6 +32,15 @@ bundle exec rubocop
|
|
|
32
32
|
- Maintain compatibility for Kerberos, LDAP, and JWT Vault auth paths.
|
|
33
33
|
- Cryptographic defaults and key lifecycle behavior are contract-sensitive; change only with test coverage.
|
|
34
34
|
|
|
35
|
+
## Known Risks
|
|
36
|
+
|
|
37
|
+
- Vault-backed cluster secret sync is inconsistent today: config key mismatch, read/write path mismatch, and push happens before the new secret is stored.
|
|
38
|
+
- External JWKS verification currently accepts tokens without issuer/audience enforcement unless the caller passes both explicitly; fail closed when touching this path.
|
|
39
|
+
- Multi-cluster Vault behavior has correctness gaps around LDAP token propagation, default-cluster routing, and lease-manager client selection.
|
|
40
|
+
- SPIFFE X.509 fetch currently falls back to a self-signed SVID on Workload API failure; treat that path as security-sensitive and avoid expanding the fallback behavior.
|
|
41
|
+
- `Ed25519` and `Erasure` include helper paths that call `Legion::Crypt::Vault.read/write` directly; verify runtime behavior before relying on those helpers.
|
|
42
|
+
- Current specs pass, but some of the highest-risk paths above are under-covered or only covered with mocks that preserve the existing behavior.
|
|
43
|
+
|
|
35
44
|
## Validation
|
|
36
45
|
|
|
37
46
|
- Run targeted specs for changed auth/crypto paths first.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Legion::Crypt
|
|
2
2
|
|
|
3
|
+
## [1.5.2] - 2026-04-03
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- 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
|
|
7
|
+
|
|
8
|
+
## [1.5.1] - 2026-04-03
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Vault `read` method no longer prepends a `legion/` mount prefix to paths — the default `type` parameter changed from `'legion'` to `nil` to match the actual KV v2 mount path in the `legionio` namespace
|
|
12
|
+
- LeaseManager now registers an `at_exit` hook to revoke active Vault leases on unclean process exit, preventing orphaned dynamic credentials (RabbitMQ users, PostgreSQL roles, Redis creds)
|
|
13
|
+
|
|
3
14
|
## [1.5.0] - 2026-04-02
|
|
4
15
|
|
|
5
16
|
### Fixed
|
|
@@ -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
|
|
@@ -24,6 +25,8 @@ module Legion
|
|
|
24
25
|
@state_mutex.synchronize { @vault_client = vault_client }
|
|
25
26
|
return if definitions.nil? || definitions.empty?
|
|
26
27
|
|
|
28
|
+
register_at_exit_hook
|
|
29
|
+
|
|
27
30
|
log.info "LeaseManager start requested definitions=#{definitions.size}"
|
|
28
31
|
definitions.each do |name, opts|
|
|
29
32
|
path = opts['path'] || opts[:path]
|
|
@@ -43,16 +46,8 @@ module Legion
|
|
|
43
46
|
next
|
|
44
47
|
end
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@active_leases[name] = {
|
|
49
|
-
lease_id: response.lease_id,
|
|
50
|
-
lease_duration: response.lease_duration,
|
|
51
|
-
renewable: response.renewable?,
|
|
52
|
-
expires_at: Time.now + (response.lease_duration || 0),
|
|
53
|
-
fetched_at: Time.now
|
|
54
|
-
}
|
|
55
|
-
end
|
|
49
|
+
log_lease_response(name, response)
|
|
50
|
+
cache_lease(name, response)
|
|
56
51
|
log.info("LeaseManager: fetched lease for '#{name}' from #{path}")
|
|
57
52
|
rescue StandardError => e
|
|
58
53
|
handle_exception(e, level: :warn, operation: 'crypt.lease_manager.start', lease_name: name, path: path)
|
|
@@ -156,6 +151,48 @@ module Legion
|
|
|
156
151
|
|
|
157
152
|
private
|
|
158
153
|
|
|
154
|
+
def register_at_exit_hook
|
|
155
|
+
return if @at_exit_registered
|
|
156
|
+
|
|
157
|
+
at_exit do
|
|
158
|
+
next if @state_mutex.synchronize { @active_leases.empty? }
|
|
159
|
+
|
|
160
|
+
Timeout.timeout(10) { shutdown }
|
|
161
|
+
rescue Timeout::Error
|
|
162
|
+
warn '[LeaseManager] at_exit shutdown timed out after 10s'
|
|
163
|
+
rescue StandardError # best effort on crash
|
|
164
|
+
nil
|
|
165
|
+
end
|
|
166
|
+
@at_exit_registered = true
|
|
167
|
+
end
|
|
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
|
+
|
|
159
196
|
def logical
|
|
160
197
|
client = @state_mutex.synchronize { @vault_client }
|
|
161
198
|
client ? client.logical : ::Vault.logical
|
data/lib/legion/crypt/vault.rb
CHANGED
|
@@ -47,7 +47,7 @@ module Legion
|
|
|
47
47
|
raise
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
def read(path, type =
|
|
50
|
+
def read(path, type = nil, cluster_name: nil)
|
|
51
51
|
full_path = type.nil? || type.empty? ? path : "#{type}/#{path}"
|
|
52
52
|
log_read_context(full_path, cluster_name: cluster_name)
|
|
53
53
|
lease = logical_client(cluster_name: cluster_name).read(full_path)
|
data/lib/legion/crypt/version.rb
CHANGED