Class: Legion::Crypt::LeaseManager

Inherits:
Object
  • Object
show all
Includes:
Logging::Helper, Singleton
Defined in:
lib/legion/crypt/lease_manager.rb

Constant Summary collapse

RENEWAL_CHECK_INTERVAL =
5

Constants included from Logging::Helper

Logging::Helper::CompatLogger

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging::Helper

#handle_exception, #log

Constructor Details

#initializeLeaseManager

Returns a new instance of LeaseManager.



15
16
17
18
19
20
21
22
# File 'lib/legion/crypt/lease_manager.rb', line 15

def initialize
  @lease_cache = {}
  @active_leases = {}
  @refs = {}
  @running = false
  @renewal_thread = nil
  @state_mutex = Mutex.new
end

Instance Attribute Details

#active_leasesObject (readonly)

Returns the value of attribute active_leases.



78
79
80
# File 'lib/legion/crypt/lease_manager.rb', line 78

def active_leases
  @active_leases
end

Instance Method Details

#fetch(name, key) ⇒ Object



65
66
67
68
69
70
71
72
# File 'lib/legion/crypt/lease_manager.rb', line 65

def fetch(name, key)
  data = @state_mutex.synchronize do
    @lease_cache[name.to_sym] || @lease_cache[name.to_s]
  end
  return nil unless data

  data[key.to_sym] || data[key.to_s]
end

#fetched_countObject



61
62
63
# File 'lib/legion/crypt/lease_manager.rb', line 61

def fetched_count
  @state_mutex.synchronize { @active_leases.size }
end

#lease_data(name) ⇒ Object



74
75
76
# File 'lib/legion/crypt/lease_manager.rb', line 74

def lease_data(name)
  @state_mutex.synchronize { @lease_cache[name] }
end

#push_to_settings(name) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/legion/crypt/lease_manager.rb', line 87

def push_to_settings(name)
  refs, data = @state_mutex.synchronize do
    r = @refs[name] || @refs[name.to_s] || @refs[name.to_sym]
    d = @lease_cache[name] || @lease_cache[name.to_s] || @lease_cache[name.to_sym]
    [r&.dup, d&.dup]
  end
  return if refs.nil? || refs.empty?
  return unless data

  refs.each do |key, path|
    value = data[key.to_sym] || data[key.to_s]
    write_setting(path, value)
  end

  log.info("Lease '#{name}' rotated — updated #{refs.size} settings reference(s)")
end

#register_dynamic_lease(name:, path:, response:, settings_refs:) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/legion/crypt/lease_manager.rb', line 127

def register_dynamic_lease(name:, path:, response:, settings_refs:)
  register_at_exit_hook

  @state_mutex.synchronize do
    @lease_cache[name] = response.data || {}
    @active_leases[name] = {
      lease_id:       response.lease_id,
      lease_duration: response.lease_duration,
      expires_at:     Time.now + (response.lease_duration || 0),
      fetched_at:     Time.now,
      renewable:      response.renewable?,
      path:           path
    }
  end
  settings_refs.each do |ref|
    register_ref(name, ref[:key], ref[:path])
  end
  log.info("LeaseManager: registered dynamic lease '#{name}' (path: #{path})")
end

#register_ref(name, key, path) ⇒ Object



80
81
82
83
84
85
# File 'lib/legion/crypt/lease_manager.rb', line 80

def register_ref(name, key, path)
  @state_mutex.synchronize do
    @refs[name] ||= {}
    @refs[name][key] = path
  end
end

#reissue_allObject



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/legion/crypt/lease_manager.rb', line 114

def reissue_all
  log.info('LeaseManager: reissue_all — re-issuing all active leases under new token')
  lease_names = @state_mutex.synchronize { @active_leases.keys.dup }

  lease_names.each do |name|
    lease = @state_mutex.synchronize { @active_leases[name]&.dup }
    next unless lease && lease[:path]

    reissue_lease(name)
  end
  log.info('LeaseManager: reissue_all complete')
end

#reissue_lease(name) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/legion/crypt/lease_manager.rb', line 147

def reissue_lease(name)
  lease = @state_mutex.synchronize { @active_leases[name]&.dup }
  unless lease && lease[:path]
    log.warn("LeaseManager: cannot reissue lease '#{name}' — no path stored for re-read")
    return
  end

  log.info("LeaseManager: reissuing lease '#{name}' from #{lease[:path]}")
  response = logical.read(lease[:path])
  unless response&.data
    log.warn("LeaseManager: reissue for '#{name}' returned no data from #{lease[:path]}")
    return
  end

  updated = @state_mutex.synchronize do
    active_lease = @active_leases[name]
    next false unless active_lease

    @lease_cache[name] = response.data
    active_lease.merge!(
      lease_id:       response.lease_id,
      lease_duration: response.lease_duration,
      expires_at:     Time.now + (response.lease_duration || 0),
      fetched_at:     Time.now,
      renewable:      response.renewable?
    )
    true
  end
  unless updated
    log.warn("LeaseManager: reissue for '#{name}' skipped — lease was removed during reissue (likely shutdown)")
    return
  end

  lease_id_preview = response.lease_id.to_s[0..11]
  log.info("LeaseManager: reissued lease '#{name}' " \
           "(new_lease_id=#{lease_id_preview}... ttl=#{response.lease_duration}s)")
  push_to_settings(name)
  trigger_reconnect(name)
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'crypt.lease_manager.reissue_lease', lease_name: name)
  log.warn("LeaseManager: failed to reissue lease '#{name}': #{e.message}")
end

#renewal_thread_alive?Boolean

Returns:

  • (Boolean)


200
201
202
# File 'lib/legion/crypt/lease_manager.rb', line 200

def renewal_thread_alive?
  @state_mutex.synchronize { @renewal_thread&.alive? || false }
end

#reset!Object



231
232
233
234
235
236
237
238
239
240
# File 'lib/legion/crypt/lease_manager.rb', line 231

def reset!
  @state_mutex.synchronize do
    @running = false
    @lease_cache.clear
    @active_leases.clear
    @refs.clear
    @vault_client = nil
    @renewal_thread = nil
  end
end

#shutdownObject



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/legion/crypt/lease_manager.rb', line 204

def shutdown
  log.info 'LeaseManager shutdown requested'
  stop_renewal_thread

  leases = @state_mutex.synchronize { @active_leases.dup }
  leases.each do |name, meta|
    lease_id = meta[:lease_id]
    next if lease_id.nil? || lease_id.empty?

    begin
      sys.revoke(lease_id)
      log.debug("LeaseManager: revoked lease '#{name}' (#{lease_id})")
    rescue StandardError => e
      handle_exception(e, level: :warn, operation: 'crypt.lease_manager.shutdown', lease_name: name)
      log.warn("LeaseManager: failed to revoke lease '#{name}' (#{lease_id}): #{e.message}")
    end
  end

  @state_mutex.synchronize do
    @lease_cache.clear
    @active_leases.clear
    @refs.clear
    @vault_client = nil
  end
  log.info 'LeaseManager shutdown complete'
end

#start(definitions, vault_client: nil) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/legion/crypt/lease_manager.rb', line 24

def start(definitions, vault_client: nil)
  @state_mutex.synchronize { @vault_client = vault_client }
  return if definitions.nil? || definitions.empty?

  register_at_exit_hook

  log.info "LeaseManager start requested definitions=#{definitions.size}"
  definitions.each do |name, opts|
    path = opts['path'] || opts[:path]
    next unless path

    if lease_valid?(name)
      log.debug("LeaseManager: reusing valid cached lease for '#{name}'")
      next
    end

    revoke_expired_lease(name)

    begin
      log.info("LeaseManager: fetching lease '#{name}' from #{path}")
      response = logical.read(path)
      unless response
        log.warn("LeaseManager: no data at '#{name}' (#{path}) — path may not exist or role not configured")
        next
      end

      log_lease_response(name, response)
      cache_lease(name, response, path: path)
      log.info("LeaseManager: fetched lease '#{name}' from #{path} " \
               "(lease_id=#{response.lease_id.to_s[0..11]}... ttl=#{response.lease_duration}s renewable=#{response.renewable?})")
    rescue StandardError => e
      handle_exception(e, level: :warn, operation: 'crypt.lease_manager.start', lease_name: name, path: path)
      log.warn("LeaseManager: failed to fetch lease '#{name}' from #{path}: #{e.message}")
    end
  end
end

#start_renewal_threadObject



190
191
192
193
194
195
196
197
198
# File 'lib/legion/crypt/lease_manager.rb', line 190

def start_renewal_thread
  @state_mutex.synchronize do
    return if @renewal_thread&.alive?

    @running = true
    @renewal_thread = Thread.new { renewal_loop }
  end
  log.info 'LeaseManager renewal thread started'
end

#vault_logicalObject

Public Vault client accessors used by Crypt for bootstrap/swap operations. Delegates to the configured vault_client or falls back to ::Vault.



106
107
108
# File 'lib/legion/crypt/lease_manager.rb', line 106

def vault_logical
  logical
end

#vault_sysObject



110
111
112
# File 'lib/legion/crypt/lease_manager.rb', line 110

def vault_sys
  sys
end