legion-crypt 1.4.18 → 1.4.20

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: 9397c4eab60b75a13e4f1e9fd842ce6b4908d94d891e9ea29a893fb33e71d5ef
4
- data.tar.gz: 852ec5569b543089412ce28e96b32f6424e7da1f169e836d3e596dd51719c74d
3
+ metadata.gz: 6b78a171584f4ccb627b63cd35cad927a6773bd1802e0da432dd445dbceb85e4
4
+ data.tar.gz: b1806202e526aa259d1491a108f3d3b94b1c4564a05ea7b4afa702778069e715
5
5
  SHA512:
6
- metadata.gz: 59170bcaead98cda37e2be65b1e9b294ab4eff65fde9251ebb2de24390224e8ed6bf3d62a523a11cd9ac1f2ead894dbffeee79815194c583e47a565b9ac7ff1c
7
- data.tar.gz: 7f65e04abcb52c320f0d6e133569604d8eba37dcd4d63bcf046b0ba8fb70c631ebf4723bce2cd8ce728bf46a7b4992af389c3cb0da25bfd9c7c1176a38e56866
6
+ metadata.gz: a1cc01154f5fef732c05efe2d99213f1742aa486e2c703f7707709dbf29ab74484b49e5be4c3a1513b116ffdb658382b70e124067771549f9dcabd8cad1fd6f1
7
+ data.tar.gz: e8e674be70714e3d439330e8d1d03b323319dd60b0f2069c26087fa772ec27c61cbc3a1ca5ed47cdff30e5ee8a826e31f16b0695f35c479184be7a02fd34e2b7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Legion::Crypt
2
2
 
3
+ ## [1.4.20] - 2026-03-27
4
+
5
+ ### Fixed
6
+ - `Vault#read`: unwrap KV v2 response envelope — `logical.read` returns `{data: {keys}, metadata: {}}` for KV v2 mounts; the nested `:data` key is now auto-detected and unwrapped
7
+
8
+ ### Added
9
+ - Debug logging throughout Vault auth, read, and cluster connection paths (`vault.rb`, `vault_cluster.rb`, `kerberos_auth.rb`, `lease_manager.rb`)
10
+ - `Vault#log_read_context`: logs path and namespace context for each Vault read
11
+ - `Vault#unwrap_kv_v2`: detects and unwraps KV v2 envelope pattern
12
+ - `VaultCluster`: debug logging for cluster connection, client build, and Kerberos auth flow
13
+ - `KerberosAuth`: debug logging for SPN, token exchange, policies, and renewal metadata
14
+ - `LeaseManager`: debug logging for lease fetch, renewal, and revocation
15
+
16
+ ## [1.4.19] - 2026-03-26
17
+
18
+ ### Fixed
19
+ - `LeaseManager`, `VaultJwtAuth`, `LdapAuth`, `VaultKerberosAuth`: use `renewable?` instead of `renewable` to match Vault gem API
20
+ - `LeaseManager#fetch`: handle string/symbol key mismatch between resolver (strings) and cache (symbols)
21
+ - `VaultCluster#connect_all_clusters`: set top-level `vault.connected` flag after any cluster connects via Kerberos/LDAP
22
+ - `Vault#add_session`: guard `@sessions` with lazy init to prevent nil error when using cluster-based auth
23
+
3
24
  ## [1.4.18] - 2026-03-26
4
25
 
5
26
  ### Fixed
@@ -17,10 +17,19 @@ module Legion
17
17
  def self.login(vault_client:, service_principal:, auth_path: DEFAULT_AUTH_PATH)
18
18
  raise GemMissingError, 'lex-kerberos gem is required for Kerberos auth' unless spnego_available?
19
19
 
20
+ log_debug("login: SPN=#{service_principal}, auth_path=#{auth_path}")
21
+ addr = vault_client.respond_to?(:address) ? vault_client.address : 'n/a'
22
+ ns = vault_client.respond_to?(:namespace) ? vault_client.namespace.inspect : 'n/a'
23
+ log_debug("login: vault_client.address=#{addr}, namespace=#{ns}")
24
+
20
25
  @kerberos_principal = nil
21
26
  token = obtain_token(service_principal)
27
+ log_debug("login: SPNEGO token obtained (#{token.length} chars)")
28
+
22
29
  result = exchange_token(vault_client, token, auth_path)
23
30
  @kerberos_principal = result[:metadata]&.dig('username') || result[:metadata]&.dig(:username)
31
+ log_debug("login: authenticated as #{@kerberos_principal.inspect}, policies=#{result[:policies].inspect}")
32
+ log_debug("login: renewable=#{result[:renewable]}, ttl=#{result[:lease_duration]}s")
24
33
  result
25
34
  end
26
35
 
@@ -41,6 +50,11 @@ module Legion
41
50
  @kerberos_principal = nil
42
51
  end
43
52
 
53
+ def self.log_debug(message)
54
+ Legion::Logging.debug("KerberosAuth: #{message}") if defined?(Legion::Logging)
55
+ end
56
+ private_class_method :log_debug
57
+
44
58
  class << self
45
59
  private
46
60
 
@@ -58,6 +72,7 @@ module Legion
58
72
 
59
73
  # The Vault Kerberos plugin reads the SPNEGO token from the HTTP
60
74
  # Authorization header, not the JSON body.
75
+ log_debug("exchange_token: PUT /v1/#{auth_path} (namespace=#{vault_client.respond_to?(:namespace) ? vault_client.namespace.inspect : 'n/a'})")
61
76
  json = vault_client.put(
62
77
  "/v1/#{auth_path}",
63
78
  '{}',
@@ -75,6 +90,7 @@ module Legion
75
90
  metadata: auth.metadata
76
91
  }
77
92
  rescue ::Vault::HTTPClientError => e
93
+ log_debug("exchange_token: HTTP error: #{e.message}")
78
94
  raise AuthError, "Vault Kerberos auth failed: #{e.message}"
79
95
  end
80
96
  end
@@ -12,10 +12,11 @@ module Legion
12
12
 
13
13
  clusters[cluster_name][:token] = token
14
14
  clusters[cluster_name][:connected] = true
15
+ mark_vault_connected
15
16
 
16
17
  Legion::Logging.info "LDAP login success: user=#{username}, cluster=#{cluster_name}" if defined?(Legion::Logging)
17
18
  { token: token, lease_duration: auth.lease_duration,
18
- renewable: auth.renewable, policies: auth.policies }
19
+ renewable: auth.renewable?, policies: auth.policies }
19
20
  rescue StandardError => e
20
21
  Legion::Logging.warn "LDAP login failed: user=#{username}, cluster=#{cluster_name}: #{e.message}" if defined?(Legion::Logging)
21
22
  raise
@@ -27,13 +27,16 @@ module Legion
27
27
 
28
28
  begin
29
29
  response = logical.read(path)
30
- next unless response
30
+ unless response
31
+ log_warn("LeaseManager: no data at '#{name}' (#{path}) — path may not exist or role not configured")
32
+ next
33
+ end
31
34
 
32
35
  @lease_cache[name] = response.data || {}
33
36
  @active_leases[name] = {
34
37
  lease_id: response.lease_id,
35
38
  lease_duration: response.lease_duration,
36
- renewable: response.renewable,
39
+ renewable: response.renewable?,
37
40
  expires_at: Time.now + (response.lease_duration || 0),
38
41
  fetched_at: Time.now
39
42
  }
@@ -44,8 +47,12 @@ module Legion
44
47
  end
45
48
  end
46
49
 
50
+ def fetched_count
51
+ @active_leases.size
52
+ end
53
+
47
54
  def fetch(name, key)
48
- data = @lease_cache[name]
55
+ data = @lease_cache[name.to_sym] || @lease_cache[name.to_s]
49
56
  return nil unless data
50
57
 
51
58
  data[key.to_sym] || data[key.to_s]
@@ -44,23 +44,34 @@ module Legion
44
44
 
45
45
  def read(path, type = 'legion')
46
46
  full_path = type.nil? || type.empty? ? "#{type}/#{path}" : path
47
- Legion::Logging.debug "Vault read: #{full_path}" if defined?(Legion::Logging)
47
+ log_read_context(full_path)
48
48
  lease = logical_client.read(full_path)
49
- add_session(path: lease.lease_id) if lease.respond_to? :lease_id
50
- lease.data
49
+ if lease.nil?
50
+ log_vault_debug("Vault read: #{full_path} returned nil")
51
+ return nil
52
+ end
53
+ add_session(path: lease.lease_id) if lease.respond_to?(:lease_id) && lease.lease_id && !lease.lease_id.empty?
54
+
55
+ data = lease.data
56
+ log_vault_debug("Vault read: #{full_path} returned keys=#{data&.keys&.inspect}")
57
+ unwrap_kv_v2(data, full_path)
51
58
  rescue StandardError => e
52
- Legion::Logging.warn "Vault read failed at #{full_path}: #{e.message}" if defined?(Legion::Logging)
59
+ Legion::Logging.warn "Vault read failed at #{full_path}: #{e.class}=#{e.message}" if defined?(Legion::Logging)
53
60
  raise
54
61
  end
55
62
 
56
63
  def get(path)
57
- Legion::Logging.debug "Vault kv get: #{path}" if defined?(Legion::Logging)
64
+ Legion::Logging.debug "Vault kv get: path=#{path}" if defined?(Legion::Logging)
58
65
  result = kv_client.read(path)
59
- return nil if result.nil?
66
+ if result.nil?
67
+ Legion::Logging.debug "Vault kv get: #{path} returned nil" if defined?(Legion::Logging)
68
+ return nil
69
+ end
60
70
 
71
+ Legion::Logging.debug "Vault kv get: #{path} returned keys=#{result.data&.keys&.inspect}" if defined?(Legion::Logging)
61
72
  result.data
62
73
  rescue StandardError => e
63
- Legion::Logging.warn "Vault kv get failed at #{path}: #{e.message}" if defined?(Legion::Logging)
74
+ Legion::Logging.warn "Vault kv get failed at #{path}: #{e.class}=#{e.message}" if defined?(Legion::Logging)
64
75
  raise
65
76
  end
66
77
 
@@ -85,6 +96,7 @@ module Legion
85
96
  end
86
97
 
87
98
  def add_session(path:)
99
+ @sessions ||= []
88
100
  @sessions.push(path)
89
101
  end
90
102
 
@@ -158,6 +170,29 @@ module Legion
158
170
  ::Vault.logical
159
171
  end
160
172
  end
173
+
174
+ def log_read_context(full_path)
175
+ return unless defined?(Legion::Logging)
176
+
177
+ namespace = if respond_to?(:connected_clusters) && connected_clusters.any?
178
+ client = vault_client
179
+ client.respond_to?(:namespace) ? client.namespace : 'n/a'
180
+ else
181
+ 'n/a (global client)'
182
+ end
183
+ Legion::Logging.debug "Vault read: path=#{full_path}, namespace=#{namespace}"
184
+ end
185
+
186
+ def unwrap_kv_v2(data, full_path)
187
+ return data unless data.is_a?(Hash) && data.key?(:data) && data[:data].is_a?(Hash) && data.key?(:metadata)
188
+
189
+ log_vault_debug("Vault read: #{full_path} detected KV v2 envelope, unwrapping :data key")
190
+ data[:data]
191
+ end
192
+
193
+ def log_vault_debug(message)
194
+ Legion::Logging.debug(message) if defined?(Legion::Logging)
195
+ end
161
196
  end
162
197
  end
163
198
  end
@@ -40,8 +40,10 @@ module Legion
40
40
  end
41
41
 
42
42
  def connect_all_clusters
43
+ log_vault_debug("connect_all_clusters: #{clusters.size} cluster(s) configured")
43
44
  results = {}
44
45
  clusters.each do |name, config|
46
+ log_vault_debug("connect_all_clusters: #{name} (auth_method=#{config[:auth_method].inspect})")
45
47
  case config[:auth_method]&.to_s
46
48
  when 'kerberos'
47
49
  results[name] = connect_kerberos_cluster(name, config)
@@ -60,11 +62,21 @@ module Legion
60
62
  results[name] = false
61
63
  log_vault_error(name, e)
62
64
  end
65
+
66
+ connected = results.select { |_, v| v }
67
+ log_vault_debug("connect_all_clusters: #{connected.size}/#{results.size} connected")
68
+ mark_vault_connected if connected.any?
63
69
  results
64
70
  end
65
71
 
66
72
  private
67
73
 
74
+ def mark_vault_connected
75
+ return unless defined?(Legion::Settings)
76
+
77
+ Legion::Settings[:crypt][:vault][:connected] = true
78
+ end
79
+
68
80
  def resolve_cluster_name(name)
69
81
  return name.to_sym if name
70
82
 
@@ -74,8 +86,10 @@ module Legion
74
86
  def build_vault_client(config)
75
87
  return nil unless config.is_a?(Hash)
76
88
 
89
+ addr = "#{config[:protocol]}://#{config[:address]}:#{config[:port]}"
90
+ log_vault_debug("build_vault_client: address=#{addr}")
77
91
  client = ::Vault::Client.new(
78
- address: "#{config[:protocol]}://#{config[:address]}:#{config[:port]}",
92
+ address: addr,
79
93
  token: config[:token]
80
94
  )
81
95
  namespace =
@@ -86,6 +100,7 @@ module Legion
86
100
  crypt_settings.respond_to?(:dig) ? crypt_settings.dig(:vault, :vault_namespace) : nil
87
101
  end
88
102
  client.namespace = namespace if namespace
103
+ log_vault_debug("build_vault_client: namespace=#{namespace.inspect}")
89
104
  client
90
105
  end
91
106
 
@@ -100,6 +115,9 @@ module Legion
100
115
  def connect_kerberos_cluster(name, config)
101
116
  krb_config = config[:kerberos] || {}
102
117
  spn = krb_config[:service_principal]
118
+ auth_path = krb_config[:auth_path] || Legion::Crypt::KerberosAuth::DEFAULT_AUTH_PATH
119
+
120
+ log_vault_debug("connect_kerberos_cluster[#{name}]: SPN=#{spn}, auth_path=#{auth_path}, namespace=#{config[:namespace].inspect}")
103
121
 
104
122
  unless spn
105
123
  log_vault_warn(name, 'Kerberos auth missing service_principal, skipping')
@@ -108,10 +126,13 @@ module Legion
108
126
  end
109
127
 
110
128
  require 'legion/crypt/kerberos_auth'
129
+ client = vault_client(name)
130
+ log_vault_debug("connect_kerberos_cluster[#{name}]: client.namespace=#{client.respond_to?(:namespace) ? client.namespace.inspect : 'n/a'}")
131
+
111
132
  result = Legion::Crypt::KerberosAuth.login(
112
- vault_client: vault_client(name),
133
+ vault_client: client,
113
134
  service_principal: spn,
114
- auth_path: krb_config[:auth_path] || Legion::Crypt::KerberosAuth::DEFAULT_AUTH_PATH
135
+ auth_path: auth_path
115
136
  )
116
137
 
117
138
  config[:token] = result[:token]
@@ -119,6 +140,7 @@ module Legion
119
140
  config[:renewable] = result[:renewable]
120
141
  config[:connected] = true
121
142
  vault_client(name).token = result[:token]
143
+ log_vault_debug("connect_kerberos_cluster[#{name}]: policies=#{result[:policies].inspect}")
122
144
  log_cluster_connected(name, config)
123
145
  true
124
146
  rescue Legion::Crypt::KerberosAuth::GemMissingError => e
@@ -142,6 +164,10 @@ module Legion
142
164
  warn("Vault cluster #{name}: #{message}")
143
165
  end
144
166
  end
167
+
168
+ def log_vault_debug(message)
169
+ Legion::Logging.debug(message) if defined?(Legion::Logging)
170
+ end
145
171
  end
146
172
  end
147
173
  end
@@ -39,7 +39,7 @@ module Legion
39
39
  {
40
40
  token: response.auth.client_token,
41
41
  lease_duration: response.auth.lease_duration,
42
- renewable: response.auth.renewable,
42
+ renewable: response.auth.renewable?,
43
43
  policies: response.auth.policies,
44
44
  metadata: response.auth.metadata
45
45
  }
@@ -16,7 +16,7 @@ module Legion
16
16
  {
17
17
  token: response.auth.client_token,
18
18
  lease_duration: response.auth.lease_duration,
19
- renewable: response.auth.renewable,
19
+ renewable: response.auth.renewable?,
20
20
  policies: response.auth.policies,
21
21
  metadata: response.auth.metadata
22
22
  }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Crypt
5
- VERSION = '1.4.18'
5
+ VERSION = '1.4.20'
6
6
  end
7
7
  end
data/lib/legion/crypt.rb CHANGED
@@ -123,7 +123,13 @@ module Legion
123
123
  lease_manager = Legion::Crypt::LeaseManager.instance
124
124
  lease_manager.start(leases, vault_client: client)
125
125
  lease_manager.start_renewal_thread
126
- Legion::Logging.info "LeaseManager: #{leases.size} lease(s) initialized"
126
+ fetched = lease_manager.fetched_count
127
+ defined = leases.size
128
+ if fetched == defined
129
+ Legion::Logging.info "LeaseManager: #{fetched} lease(s) initialized"
130
+ else
131
+ Legion::Logging.warn "LeaseManager: #{fetched}/#{defined} lease(s) initialized (#{defined - fetched} failed)"
132
+ end
127
133
  rescue StandardError => e
128
134
  Legion::Logging.warn "LeaseManager startup failed: #{e.message}"
129
135
  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.4.18
4
+ version: 1.4.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity