legion-crypt 1.4.28 → 1.5.0

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.
data/lib/legion/crypt.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'openssl'
4
4
  require 'base64'
5
+ require 'legion/logging/helper'
5
6
  require 'legion/crypt/version'
6
7
  require 'legion/crypt/settings'
7
8
  require 'legion/crypt/cipher'
@@ -27,6 +28,7 @@ module Legion
27
28
  class << self
28
29
  attr_reader :sessions
29
30
 
31
+ include Legion::Logging::Helper
30
32
  include Legion::Crypt::Cipher
31
33
 
32
34
  unless Gem::Specification.find_by_name('vault').nil?
@@ -57,18 +59,30 @@ module Legion
57
59
  end
58
60
 
59
61
  def start
60
- Legion::Logging.debug 'Legion::Crypt is running start'
61
- ::File.write('./legionio.key', private_key) if settings[:save_private_key]
62
- @token_renewers ||= []
63
-
64
- if vault_settings[:clusters]&.any?
65
- connect_all_clusters
66
- start_token_renewers
67
- else
68
- connect_vault unless settings[:vault][:token].nil?
62
+ lifecycle_mutex.synchronize do
63
+ if @started
64
+ log.info 'Legion::Crypt start ignored because the lifecycle is already running'
65
+ return
66
+ end
67
+
68
+ log.info 'Legion::Crypt startup initiated'
69
+ log.debug 'Legion::Crypt start requested'
70
+ ::File.write('./legionio.key', private_key) if settings[:save_private_key]
71
+ @token_renewers ||= []
72
+
73
+ if vault_settings[:clusters]&.any?
74
+ log.info "Legion::Crypt connecting #{vault_settings[:clusters].size} Vault cluster(s)"
75
+ connect_all_clusters
76
+ start_token_renewers
77
+ else
78
+ log.info 'Legion::Crypt connecting primary Vault client' unless settings[:vault][:token].nil?
79
+ connect_vault unless settings[:vault][:token].nil?
80
+ end
81
+ start_lease_manager
82
+ start_svid_rotation
83
+ @started = true
84
+ log.info 'Legion::Crypt startup completed'
69
85
  end
70
- start_lease_manager
71
- start_svid_rotation
72
86
  end
73
87
 
74
88
  def settings
@@ -83,6 +97,16 @@ module Legion
83
97
  settings[:jwt] || Legion::Crypt::Settings.jwt
84
98
  end
85
99
 
100
+ def vault_connected?
101
+ return true if settings.dig(:vault, :connected) == true
102
+ return true if respond_to?(:connected_clusters) && connected_clusters.any?
103
+
104
+ false
105
+ rescue StandardError => e
106
+ handle_exception(e, level: :debug, operation: 'crypt.vault_connected?')
107
+ false
108
+ end
109
+
86
110
  def issue_token(payload = {}, ttl: nil, algorithm: nil)
87
111
  jwt = jwt_settings
88
112
  algo = algorithm || jwt[:default_algorithm]
@@ -111,11 +135,21 @@ module Legion
111
135
  end
112
136
 
113
137
  def shutdown
114
- Legion::Crypt::LeaseManager.instance.shutdown
115
- stop_token_renewers
116
- shutdown_renewer
117
- close_sessions
118
- stop_svid_rotation
138
+ lifecycle_mutex.synchronize do
139
+ unless @started
140
+ log.info 'Legion::Crypt shutdown ignored because the lifecycle is not running'
141
+ return
142
+ end
143
+
144
+ log.info 'Legion::Crypt shutdown initiated'
145
+ Legion::Crypt::LeaseManager.instance.shutdown
146
+ stop_token_renewers
147
+ shutdown_renewer
148
+ close_sessions
149
+ stop_svid_rotation
150
+ @started = false
151
+ log.info 'Legion::Crypt shutdown completed'
152
+ end
119
153
  end
120
154
 
121
155
  private
@@ -123,22 +157,15 @@ module Legion
123
157
  def start_lease_manager
124
158
  leases = settings.dig(:vault, :leases) || {}
125
159
  return if leases.empty?
126
- return unless settings.dig(:vault, :connected) || connected_clusters.any?
160
+ return unless connected_clusters.any? || settings.dig(:vault, :connected)
127
161
 
128
162
  client = nil
129
163
 
130
- if settings.dig(:vault, :connected)
131
- client = vault_client
132
- elsif connected_clusters.any?
133
- default_cluster = vault_settings[:default]
134
- selected_cluster =
135
- if default_cluster && connected_clusters.include?(default_cluster.to_sym)
136
- default_cluster.to_sym
137
- else
138
- connected_clusters.keys.first
139
- end
140
-
164
+ if connected_clusters.any?
165
+ selected_cluster = selected_connected_cluster_name
141
166
  client = selected_cluster ? vault_client(selected_cluster) : nil
167
+ elsif settings.dig(:vault, :connected)
168
+ client = vault_client
142
169
  end
143
170
  lease_manager = Legion::Crypt::LeaseManager.instance
144
171
  lease_manager.start(leases, vault_client: client)
@@ -146,15 +173,16 @@ module Legion
146
173
  fetched = lease_manager.fetched_count
147
174
  defined = leases.size
148
175
  if fetched == defined
149
- Legion::Logging.info "LeaseManager: #{fetched} lease(s) initialized"
176
+ log.info "LeaseManager: #{fetched} lease(s) initialized"
150
177
  else
151
- Legion::Logging.warn "LeaseManager: #{fetched}/#{defined} lease(s) initialized (#{defined - fetched} failed)"
178
+ log.warn "LeaseManager: #{fetched}/#{defined} lease(s) initialized (#{defined - fetched} failed)"
152
179
  end
153
180
  rescue StandardError => e
154
- Legion::Logging.warn "LeaseManager startup failed: #{e.message}"
181
+ handle_exception(e, level: :warn, operation: 'crypt.start_lease_manager')
155
182
  end
156
183
 
157
184
  def start_token_renewers
185
+ started = 0
158
186
  clusters.each do |name, config|
159
187
  next unless config[:auth_method]&.to_s == 'kerberos' && config[:connected]
160
188
 
@@ -165,31 +193,40 @@ module Legion
165
193
  )
166
194
  renewer.start
167
195
  @token_renewers << renewer
196
+ started += 1
168
197
  end
198
+ log.info "Legion::Crypt started #{started} token renewer(s)" if started.positive?
169
199
  end
170
200
 
171
201
  def stop_token_renewers
172
202
  return unless @token_renewers
173
203
 
174
204
  @token_renewers.each(&:stop)
205
+ log.info "Legion::Crypt stopped #{@token_renewers.size} token renewer(s)" if @token_renewers.any?
175
206
  @token_renewers.clear
176
207
  end
177
208
 
178
209
  def start_svid_rotation
179
210
  return unless Spiffe.enabled?
180
211
 
212
+ log.info 'Legion::Crypt starting SPIFFE SVID rotation'
181
213
  @svid_rotation = Spiffe::SvidRotation.new
182
214
  @svid_rotation.start
183
215
  rescue StandardError => e
184
- Legion::Logging.warn "SPIFFE SvidRotation startup failed: #{e.message}" if defined?(Legion::Logging)
216
+ handle_exception(e, level: :warn, operation: 'crypt.start_svid_rotation')
185
217
  end
186
218
 
187
219
  def stop_svid_rotation
188
220
  return unless @svid_rotation
189
221
 
222
+ log.info 'Legion::Crypt stopping SPIFFE SVID rotation'
190
223
  @svid_rotation.stop
191
224
  @svid_rotation = nil
192
225
  end
226
+
227
+ def lifecycle_mutex
228
+ @lifecycle_mutex ||= Mutex.new
229
+ end
193
230
  end
194
231
  end
195
232
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ helper_path = File.join(
5
+ Gem::Specification.find_by_name('legion-logging').full_gem_path,
6
+ 'lib/legion/logging/helper.rb'
7
+ )
8
+ require helper_path if File.exist?(helper_path)
9
+ rescue Gem::LoadError
10
+ nil
11
+ end
12
+
13
+ require 'legion/logging'
14
+
15
+ module Legion
16
+ module Logging
17
+ module Helper
18
+ unless const_defined?(:CompatLogger, false)
19
+ CompatLogger = Class.new do
20
+ %i[debug info warn error fatal unknown].each do |level|
21
+ define_method(level) do |message = nil, &block|
22
+ payload = block ? block.call : message
23
+ return if payload.nil?
24
+
25
+ if logging_supports?(level)
26
+ Legion::Logging.public_send(level, payload)
27
+ elsif %i[error fatal warn].include?(level)
28
+ ::Kernel.warn(payload)
29
+ else
30
+ $stdout.puts(payload)
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def logging_supports?(level)
38
+ return false unless Legion.const_defined?('Logging')
39
+
40
+ Legion::Logging.respond_to?(level)
41
+ rescue StandardError
42
+ false
43
+ end
44
+ end
45
+ end
46
+
47
+ def log
48
+ @log ||= CompatLogger.new
49
+ end
50
+
51
+ def handle_exception(exception, task_id: nil, level: :error, handled: true, **opts) # rubocop:disable Lint/UnusedMethodArgument,Style/ArgumentsForwarding
52
+ message = exception_log_message(exception, level: level, **opts) # rubocop:disable Style/ArgumentsForwarding
53
+
54
+ if logging_supports?(:log_exception)
55
+ Legion::Logging.log_exception(exception, lex: 'crypt', component_type: :helper)
56
+ return
57
+ end
58
+ if logging_supports?(level)
59
+ Legion::Logging.public_send(level, message)
60
+ return
61
+ end
62
+ if logging_supports?(:error)
63
+ Legion::Logging.error(message)
64
+ return
65
+ end
66
+ if logging_supports?(:warn)
67
+ Legion::Logging.warn(message)
68
+ return
69
+ end
70
+
71
+ ::Kernel.warn(message)
72
+ end
73
+
74
+ private
75
+
76
+ def logging_supports?(level)
77
+ return false unless Legion.const_defined?('Logging')
78
+
79
+ Legion::Logging.respond_to?(level)
80
+ rescue StandardError
81
+ false
82
+ end
83
+
84
+ def exception_log_message(exception, level:, **opts)
85
+ operation = opts[:operation] || opts['operation']
86
+ prefix = operation ? "#{operation} failed: " : ''
87
+ details = opts.reject { |key, _value| key.to_s == 'operation' }.map { |key, value| "#{key}=#{value}" }
88
+ detail_suffix = details.empty? ? '' : " (#{details.join(' ')})"
89
+ backtrace = Array(exception.backtrace).first(10).join("\n")
90
+ base = "#{prefix}#{exception.class}: #{exception.message}#{detail_suffix}"
91
+ return base if backtrace.empty? && level == :debug
92
+ return base if backtrace.empty?
93
+
94
+ "#{base}\n#{backtrace}"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ begin
6
+ gem_root = Gem::Specification.find_by_name('legion-logging').full_gem_path
7
+ upstream_logging = File.join(gem_root, 'lib/legion/logging.rb')
8
+ require upstream_logging if File.exist?(upstream_logging)
9
+ rescue Gem::LoadError
10
+ nil
11
+ end
12
+
13
+ module Legion
14
+ module Logging
15
+ class << self
16
+ unless method_defined?(:setup)
17
+ def setup(level: 'info', **_opts)
18
+ logger.level = normalize_level(level)
19
+ self
20
+ end
21
+
22
+ def logger
23
+ @logger ||= Logger.new($stdout).tap do |instance|
24
+ instance.progname = 'legion-crypt'
25
+ end
26
+ end
27
+
28
+ def log_exception(exception, lex: nil, component_type: nil, **_opts)
29
+ prefix = [lex, component_type].compact.join('.')
30
+ payload = prefix.empty? ? exception.message : "#{prefix}: #{exception.message}"
31
+ error(payload)
32
+ end
33
+
34
+ %i[debug info warn error fatal unknown].each do |level_name|
35
+ define_method(level_name) do |message = nil, &block|
36
+ payload = block ? block.call : message
37
+ return if payload.nil?
38
+
39
+ logger.public_send(level_name, payload)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def normalize_level(level)
46
+ case level.to_s.downcase
47
+ when 'debug' then Logger::DEBUG
48
+ when 'info' then Logger::INFO
49
+ when 'warn' then Logger::WARN
50
+ when 'error' then Logger::ERROR
51
+ when 'fatal' then Logger::FATAL
52
+ else Logger::UNKNOWN
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ 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.28
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -37,6 +37,20 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '2.7'
40
+ - !ruby/object:Gem::Dependency
41
+ name: legion-logging
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 1.5.0
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.5.0
40
54
  - !ruby/object:Gem::Dependency
41
55
  name: vault
42
56
  requirement: !ruby/object:Gem::Requirement
@@ -103,6 +117,8 @@ files:
103
117
  - lib/legion/crypt/vault_kerberos_auth.rb
104
118
  - lib/legion/crypt/vault_renewer.rb
105
119
  - lib/legion/crypt/version.rb
120
+ - lib/legion/logging.rb
121
+ - lib/legion/logging/helper.rb
106
122
  - sonar-project.properties
107
123
  homepage: https://github.com/LegionIO/legion-crypt
108
124
  licenses: