legion-crypt 0.3.0 → 1.3.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.
@@ -1,8 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'securerandom'
4
+ require 'legion/crypt/cluster_secret'
2
5
 
3
6
  module Legion
4
7
  module Crypt
5
8
  module Cipher
9
+ include Legion::Crypt::ClusterSecret
10
+
6
11
  def encrypt(message)
7
12
  cipher = OpenSSL::Cipher.new('aes-256-cbc')
8
13
  cipher.encrypt
@@ -11,7 +16,7 @@ module Legion
11
16
  { enciphered_message: Base64.encode64(cipher.update(message) + cipher.final), iv: Base64.encode64(iv) }
12
17
  end
13
18
 
14
- def decrypt(message, iv)
19
+ def decrypt(message, init_vector)
15
20
  until cs.is_a?(String) || Legion::Settings[:client][:shutting_down]
16
21
  Legion::Logging.debug('sleeping Legion::Crypt.decrypt due to CS not being set')
17
22
  sleep(0.5)
@@ -20,7 +25,7 @@ module Legion
20
25
  decipher = OpenSSL::Cipher.new('aes-256-cbc')
21
26
  decipher.decrypt
22
27
  decipher.key = cs
23
- decipher.iv = Base64.decode64(iv)
28
+ decipher.iv = Base64.decode64(init_vector)
24
29
  message = Base64.decode64(message)
25
30
  decipher.update(message) + decipher.final
26
31
  end
@@ -46,58 +51,6 @@ module Legion
46
51
  OpenSSL::PKey::RSA.new 2048
47
52
  end
48
53
  end
49
-
50
- def cs
51
- @cs ||= Digest::SHA256.digest(fetch_cs)
52
- end
53
-
54
- def fetch_cs # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity
55
- if Legion::Settings[:crypt][:vault][:read_cluster_secret] && Legion::Settings[:crypt][:vault][:connected] && Legion::Crypt.exist?('crypt') # rubocop:disable Layout/LineLength
56
- Legion::Crypt.get('crypt')[:cluster_secret]
57
- elsif Legion::Settings[:crypt][:cluster_secret].is_a? String
58
- Legion::Settings[:crypt][:cluster_secret]
59
- elsif Legion::Transport::Queue.new('node.crypt', passive: true).consumer_count.zero?
60
- Legion::Settings[:crypt][:cluster_secret] = generate_secure_random
61
- elsif Legion::Transport::Queue.new('node.crypt', passive: true).consumer_count.positive?
62
- require 'legion/transport/messages/request_cluster_secret'
63
- Legion::Logging.info 'Requesting cluster secret via public key'
64
- start = Time.now
65
- Legion::Transport::Messages::RequestClusterSecret.new.publish
66
- sleep_time = 0.001
67
- until !Legion::Settings[:crypt][:cluster_secret].nil? || (Time.now - start) > Legion::Settings[:crypt][:cluster_secret_timeout]
68
- sleep(sleep_time)
69
- sleep_time *= 2 unless sleep_time > 0.5
70
- end
71
-
72
- if Legion::Settings[:crypt][:cluster_secret].nil?
73
- Legion::Logging.warn 'Cluster secret is still nil'
74
- else
75
- Legion::Logging.info "Received cluster secret in #{((Time.new - start) * 1000.0).round}ms"
76
- end
77
- end
78
- rescue StandardError => e
79
- Legion::Logging.error(e.message)
80
- Legion::Logging.error(e.backtrace)
81
- ensure
82
- Legion::Settings[:crypt][:cluster_secret] = generate_secure_random unless Legion::Settings[:crypt].key? :cluster_secret
83
- nil if Legion::Settings[:crypt][:cluster_secret].nil?
84
-
85
- Legion::Settings[:crypt][:cs_encrypt_ready] = true
86
- push_cs_to_vault if Legion::Settings[:crypt][:vault][:push_cs_to_vault]
87
-
88
- return Legion::Settings[:crypt][:cluster_secret] # rubocop:disable Lint/EnsureReturn
89
- end
90
-
91
- def push_cs_to_vault
92
- return false unless Legion::Settings[:crypt][:vault][:connected] && Legion::Settings[:crypt][:cluster_secret]
93
-
94
- Legion::Logging.info 'Pushing Cluster Secret to Vault'
95
- Legion::Crypt.write('cluster', secret: Legion::Settings[:crypt][:cluster_secret])
96
- end
97
-
98
- def generate_secure_random
99
- SecureRandom.uuid
100
- end
101
54
  end
102
55
  end
103
56
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Crypt
7
+ module ClusterSecret
8
+ def find_cluster_secret
9
+ %i[from_settings from_vault from_transport generate_secure_random].each do |method|
10
+ result = send(method)
11
+ next if result.nil?
12
+
13
+ unless validate_hex(result)
14
+ Legion::Logging.warn("Legion::Crypt.#{method} gave a value but it isn't a valid hex")
15
+ next
16
+ end
17
+
18
+ set_cluster_secret(result, method != :from_vault)
19
+ return result
20
+ end
21
+ return unless only_member?
22
+
23
+ key = generate_secure_random
24
+ set_cluster_secret(key)
25
+ key
26
+ end
27
+
28
+ def from_vault
29
+ return nil unless method_defined? :get
30
+ return nil unless Legion::Settings[:crypt][:vault][:read_cluster_secret]
31
+ return nil unless Legion::Settings[:crypt][:vault][:connected]
32
+ return nil unless Legion::Crypt.exist?('crypt')
33
+
34
+ get('crypt')[:cluster_secret]
35
+ rescue StandardError
36
+ nil
37
+ end
38
+
39
+ def from_settings
40
+ Legion::Settings[:crypt][:cluster_secret]
41
+ end
42
+ alias cluster_secret from_settings
43
+
44
+ def from_transport
45
+ return nil unless Legion::Settings[:transport][:connected]
46
+
47
+ require 'legion/transport/messages/request_cluster_secret'
48
+ Legion::Logging.info 'Requesting cluster secret via public key'
49
+ Legion::Logging.warn 'cluster_secret already set but we are requesting a new value' unless from_settings.nil?
50
+ start = Time.now
51
+ Legion::Transport::Messages::RequestClusterSecret.new.publish
52
+ sleep_time = 0.001
53
+ until !Legion::Settings[:crypt][:cluster_secret].nil? || (Time.now - start) > cluster_secret_timeout
54
+ sleep(sleep_time)
55
+ sleep_time *= 2 unless sleep_time > 0.5
56
+ end
57
+
58
+ unless from_settings.nil?
59
+ Legion::Logging.info "Received cluster secret in #{((Time.new - start) * 1000.0).round}ms"
60
+ return from_settings
61
+ end
62
+
63
+ Legion::Logging.error 'Cluster secret is still unknown!'
64
+ nil
65
+ rescue StandardError => e
66
+ Legion::Logging.error e.message
67
+ Legion::Logging.error e.backtrace[0..10]
68
+ end
69
+
70
+ def force_cluster_secret
71
+ Legion::Settings[:crypt][:force_cluster_secret] || true
72
+ end
73
+
74
+ def settings_push_vault
75
+ Legion::Settings[:crypt][:vault][:push_cs_to_vault] || true
76
+ end
77
+
78
+ def only_member?
79
+ Legion::Transport::Queue.new('node.crypt', passive: true).consumer_count.zero?
80
+ rescue StandardError
81
+ nil
82
+ end
83
+
84
+ def set_cluster_secret(value, push_to_vault = true) # rubocop:disable Style/OptionalBooleanParameter
85
+ raise TypeError unless value.to_i(32).to_s(32).rjust(value.length, '0') == value.downcase
86
+
87
+ Legion::Settings[:crypt][:cs_encrypt_ready] = true
88
+ push_cs_to_vault if push_to_vault && settings_push_vault
89
+
90
+ Legion::Settings[:crypt][:cluster_secret] = value
91
+ end
92
+
93
+ def push_cs_to_vault
94
+ return false unless Legion::Settings[:crypt][:vault][:connected] && Legion::Settings[:crypt][:cluster_secret]
95
+
96
+ Legion::Logging.info 'Pushing Cluster Secret to Vault'
97
+ Legion::Crypt.write('cluster', secret: Legion::Settings[:crypt][:cluster_secret])
98
+ end
99
+
100
+ def cluster_secret_timeout
101
+ Legion::Settings[:crypt][:cluster_secret_timeout] || 5
102
+ end
103
+
104
+ def secret_length
105
+ Legion::Settings[:crypt][:cluster_lenth] || 32
106
+ end
107
+
108
+ def generate_secure_random(length = secret_length)
109
+ SecureRandom.hex(length)
110
+ end
111
+
112
+ def cs
113
+ @cs ||= Digest::SHA256.digest(find_cluster_secret)
114
+ rescue StandardError => e
115
+ Legion::Logging.error e.message
116
+ Legion::Logging.error e.backtrace[0..10]
117
+ end
118
+
119
+ def validate_hex(value, length = secret_length)
120
+ return false unless value.is_a?(String)
121
+ return false if value.empty?
122
+
123
+ value.to_i(length).to_s(length).rjust(value.length, '0') == value.downcase
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+ require 'securerandom'
5
+
6
+ module Legion
7
+ module Crypt
8
+ module JWT
9
+ class Error < StandardError; end
10
+ class ExpiredTokenError < Error; end
11
+ class InvalidTokenError < Error; end
12
+ class DecodeError < Error; end
13
+
14
+ SUPPORTED_ALGORITHMS = %w[HS256 RS256].freeze
15
+
16
+ def self.issue(payload, signing_key:, algorithm: 'HS256', ttl: 3600, issuer: 'legion')
17
+ validate_algorithm!(algorithm)
18
+
19
+ now = Time.now.to_i
20
+ claims = {
21
+ iss: issuer,
22
+ iat: now,
23
+ exp: now + ttl,
24
+ jti: SecureRandom.uuid
25
+ }.merge(payload)
26
+
27
+ ::JWT.encode(claims, signing_key, algorithm)
28
+ end
29
+
30
+ def self.verify(token, verification_key:, **opts)
31
+ algorithm = opts.fetch(:algorithm, 'HS256')
32
+ verify_expiration = opts.fetch(:verify_expiration, true)
33
+ verify_issuer = opts.fetch(:verify_issuer, true)
34
+ issuer = opts.fetch(:issuer, 'legion')
35
+
36
+ validate_algorithm!(algorithm)
37
+
38
+ decode_opts = {
39
+ algorithm: algorithm,
40
+ verify_expiration: verify_expiration,
41
+ verify_iss: verify_issuer
42
+ }
43
+ decode_opts[:iss] = issuer if verify_issuer
44
+
45
+ payload, _header = ::JWT.decode(token, verification_key, true, decode_opts)
46
+ symbolize_keys(payload)
47
+ rescue ::JWT::ExpiredSignature
48
+ raise ExpiredTokenError, 'token has expired'
49
+ rescue ::JWT::VerificationError, ::JWT::IncorrectAlgorithm
50
+ raise InvalidTokenError, 'token signature verification failed'
51
+ rescue ::JWT::DecodeError => e
52
+ raise DecodeError, "failed to decode token: #{e.message}"
53
+ end
54
+
55
+ def self.decode(token)
56
+ payload, _header = ::JWT.decode(token, nil, false)
57
+ symbolize_keys(payload)
58
+ rescue ::JWT::DecodeError => e
59
+ raise DecodeError, "failed to decode token: #{e.message}"
60
+ end
61
+
62
+ def self.validate_algorithm!(algorithm)
63
+ return if SUPPORTED_ALGORITHMS.include?(algorithm)
64
+
65
+ raise ArgumentError, "unsupported algorithm: #{algorithm}. Supported: #{SUPPORTED_ALGORITHMS.join(', ')}"
66
+ end
67
+
68
+ def self.symbolize_keys(hash)
69
+ hash.transform_keys(&:to_sym)
70
+ end
71
+
72
+ private_class_method :validate_algorithm!, :symbolize_keys
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+
5
+ module Legion
6
+ module Crypt
7
+ class LeaseManager
8
+ include Singleton
9
+
10
+ RENEWAL_CHECK_INTERVAL = 5
11
+
12
+ def initialize
13
+ @lease_cache = {}
14
+ @active_leases = {}
15
+ @refs = {}
16
+ @running = false
17
+ @renewal_thread = nil
18
+ end
19
+
20
+ def start(definitions)
21
+ return if definitions.nil? || definitions.empty?
22
+
23
+ definitions.each do |name, opts|
24
+ path = opts['path'] || opts[:path]
25
+ next unless path
26
+
27
+ begin
28
+ response = ::Vault.logical.read(path)
29
+ next unless response
30
+
31
+ @lease_cache[name] = response.data || {}
32
+ @active_leases[name] = {
33
+ lease_id: response.lease_id,
34
+ lease_duration: response.lease_duration,
35
+ renewable: response.renewable,
36
+ expires_at: Time.now + (response.lease_duration || 0),
37
+ fetched_at: Time.now
38
+ }
39
+ log_debug("LeaseManager: fetched lease for '#{name}' from #{path}")
40
+ rescue StandardError => e
41
+ log_warn("LeaseManager: failed to fetch lease '#{name}' from #{path}: #{e.message}")
42
+ end
43
+ end
44
+ end
45
+
46
+ def fetch(name, key)
47
+ data = @lease_cache[name]
48
+ return nil unless data
49
+
50
+ data[key.to_sym] || data[key.to_s]
51
+ end
52
+
53
+ def lease_data(name)
54
+ @lease_cache[name]
55
+ end
56
+
57
+ attr_reader :active_leases
58
+
59
+ def register_ref(name, key, path)
60
+ @refs[name] ||= {}
61
+ @refs[name][key] = path
62
+ end
63
+
64
+ def push_to_settings(name)
65
+ refs = @refs[name]
66
+ return if refs.nil? || refs.empty?
67
+
68
+ data = @lease_cache[name]
69
+ return unless data
70
+
71
+ refs.each do |key, path|
72
+ value = data[key.to_sym] || data[key.to_s]
73
+ write_setting(path, value)
74
+ end
75
+
76
+ log_debug("Lease '#{name}' rotated — updated #{refs.size} settings reference(s)")
77
+ end
78
+
79
+ def start_renewal_thread
80
+ return if renewal_thread_alive?
81
+
82
+ @running = true
83
+ @renewal_thread = Thread.new { renewal_loop }
84
+ end
85
+
86
+ def renewal_thread_alive?
87
+ @renewal_thread&.alive? || false
88
+ end
89
+
90
+ def shutdown
91
+ stop_renewal_thread
92
+
93
+ @active_leases.each do |name, meta|
94
+ lease_id = meta[:lease_id]
95
+ next if lease_id.nil? || lease_id.empty?
96
+
97
+ begin
98
+ ::Vault.sys.revoke(lease_id)
99
+ log_debug("LeaseManager: revoked lease '#{name}' (#{lease_id})")
100
+ rescue StandardError => e
101
+ log_warn("LeaseManager: failed to revoke lease '#{name}' (#{lease_id}): #{e.message}")
102
+ end
103
+ end
104
+
105
+ @lease_cache.clear
106
+ @active_leases.clear
107
+ @refs.clear
108
+ end
109
+
110
+ def reset!
111
+ @running = false
112
+ @lease_cache.clear
113
+ @active_leases.clear
114
+ @refs.clear
115
+ end
116
+
117
+ private
118
+
119
+ def stop_renewal_thread
120
+ @running = false
121
+ if @renewal_thread&.alive?
122
+ @renewal_thread.kill
123
+ @renewal_thread.join(2)
124
+ end
125
+ @renewal_thread = nil
126
+ end
127
+
128
+ def renewal_loop
129
+ while @running
130
+ sleep(RENEWAL_CHECK_INTERVAL)
131
+ renew_approaching_leases if @running
132
+ end
133
+ rescue StandardError => e
134
+ log_warn("LeaseManager: renewal loop error: #{e.message}")
135
+ retry if @running
136
+ end
137
+
138
+ def renew_approaching_leases
139
+ @active_leases.each do |name, lease|
140
+ next unless lease[:renewable]
141
+ next unless approaching_expiry?(lease)
142
+
143
+ renew_lease(name, lease)
144
+ end
145
+ end
146
+
147
+ def renew_lease(name, lease)
148
+ response = ::Vault.sys.renew(lease[:lease_id])
149
+ lease[:expires_at] = Time.now + (response.lease_duration || 0)
150
+
151
+ if response.data && response.data != @lease_cache[name]
152
+ @lease_cache[name] = response.data
153
+ push_to_settings(name)
154
+ end
155
+ rescue StandardError => e
156
+ log_warn("LeaseManager: failed to renew lease '#{name}': #{e.message}")
157
+ end
158
+
159
+ def approaching_expiry?(lease)
160
+ expires_at = lease[:expires_at]
161
+ lease_duration = lease[:lease_duration]
162
+
163
+ return true if expires_at.nil? || lease_duration.nil?
164
+
165
+ remaining = expires_at - Time.now
166
+ remaining < (lease_duration * 0.5)
167
+ end
168
+
169
+ def write_setting(path, value)
170
+ return if path.nil? || path.empty?
171
+
172
+ target = path[1..-2].reduce(Legion::Settings[path[0]]) do |node, segment|
173
+ break nil unless node.is_a?(Hash)
174
+
175
+ node[segment]
176
+ end
177
+ target[path.last] = value if target.is_a?(Hash)
178
+ rescue StandardError => e
179
+ log_warn("LeaseManager: failed to write setting at #{path.join('.')}: #{e.message}")
180
+ end
181
+
182
+ def log_debug(message)
183
+ if defined?(Legion::Logging)
184
+ Legion::Logging.debug(message)
185
+ else
186
+ $stdout.puts("[DEBUG] #{message}")
187
+ end
188
+ end
189
+
190
+ def log_warn(message)
191
+ if defined?(Legion::Logging)
192
+ Legion::Logging.warn(message)
193
+ else
194
+ warn("[WARN] #{message}")
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -1,30 +1,45 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Legion
2
4
  module Crypt
3
5
  module Settings
4
6
  def self.default
5
7
  {
6
- vault: vault,
8
+ vault: vault,
9
+ jwt: jwt,
7
10
  cs_encrypt_ready: false,
8
- dynamic_keys: true,
9
- cluster_secret: nil,
11
+ dynamic_keys: true,
12
+ cluster_secret: nil,
10
13
  save_private_key: true,
11
14
  read_private_key: true
12
15
  }
13
16
  end
14
17
 
18
+ def self.jwt
19
+ {
20
+ enabled: true,
21
+ default_algorithm: 'HS256',
22
+ default_ttl: 3600,
23
+ issuer: 'legion',
24
+ verify_expiration: true,
25
+ verify_issuer: true
26
+ }
27
+ end
28
+
15
29
  def self.vault
16
30
  {
17
- enabled: !Gem::Specification.find_by_name('vault').nil?,
18
- protocol: 'http',
19
- address: 'localhost',
20
- port: 8200,
21
- token: ENV['VAULT_DEV_ROOT_TOKEN_ID'] || ENV['VAULT_TOKEN_ID'] || nil,
22
- connected: false,
23
- renewer_time: 5,
24
- renewer: true,
31
+ enabled: !Gem::Specification.find_by_name('vault').nil?,
32
+ protocol: 'http',
33
+ address: 'localhost',
34
+ port: 8200,
35
+ token: ENV['VAULT_DEV_ROOT_TOKEN_ID'] || ENV['VAULT_TOKEN_ID'] || nil,
36
+ connected: false,
37
+ renewer_time: 5,
38
+ renewer: true,
25
39
  push_cluster_secret: true,
26
40
  read_cluster_secret: true,
27
- kv_path: ENV['LEGION_VAULT_KV_PATH'] || 'legion'
41
+ kv_path: ENV['LEGION_VAULT_KV_PATH'] || 'legion',
42
+ leases: {}
28
43
  }
29
44
  end
30
45
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'vault'
2
4
 
3
5
  module Legion
@@ -9,7 +11,7 @@ module Legion
9
11
  Legion::Settings[:crypt][:vault]
10
12
  end
11
13
 
12
- def connect_vault # rubocop:disable Metrics/AbcSize
14
+ def connect_vault
13
15
  @sessions = []
14
16
  ::Vault.address = "#{Legion::Settings[:crypt][:vault][:protocol]}://#{Legion::Settings[:crypt][:vault][:address]}:#{Legion::Settings[:crypt][:vault][:port]}" # rubocop:disable Layout/LineLength
15
17
 
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Crypt
5
+ # Vault JWT auth backend integration.
6
+ #
7
+ # Allows Legion workers to authenticate to Vault using JWT tokens
8
+ # via Vault's JWT/OIDC auth method. The worker presents a signed JWT
9
+ # and receives a Vault token with policies scoped to the worker's role.
10
+ #
11
+ # Vault config prerequisites:
12
+ # vault auth enable jwt
13
+ # vault write auth/jwt/config jwks_url="..." (or bound_issuer + jwt_validation_pubkeys)
14
+ # vault write auth/jwt/role/legion-worker bound_audiences="legion" ...
15
+ module VaultJwtAuth
16
+ DEFAULT_AUTH_PATH = 'auth/jwt/login'
17
+ DEFAULT_ROLE = 'legion-worker'
18
+
19
+ class AuthError < StandardError; end
20
+
21
+ # Authenticate to Vault using a JWT token.
22
+ # Returns a Vault token string on success.
23
+ #
24
+ # @param jwt [String] Signed JWT token (issued by Legion or Entra ID)
25
+ # @param role [String] Vault JWT auth role name (default: 'legion-worker')
26
+ # @param auth_path [String] Vault auth mount path (default: 'auth/jwt/login')
27
+ # @return [Hash] { token:, lease_duration:, policies:, metadata: }
28
+ def self.login(jwt:, role: DEFAULT_ROLE, auth_path: DEFAULT_AUTH_PATH)
29
+ raise AuthError, 'Vault is not connected' unless vault_connected?
30
+
31
+ response = ::Vault.logical.write(
32
+ auth_path,
33
+ role: role,
34
+ jwt: jwt
35
+ )
36
+
37
+ raise AuthError, 'Vault JWT auth returned no auth data' unless response&.auth
38
+
39
+ {
40
+ token: response.auth.client_token,
41
+ lease_duration: response.auth.lease_duration,
42
+ renewable: response.auth.renewable,
43
+ policies: response.auth.policies,
44
+ metadata: response.auth.metadata
45
+ }
46
+ rescue ::Vault::HTTPClientError => e
47
+ raise AuthError, "Vault JWT auth failed: #{e.message}"
48
+ rescue ::Vault::HTTPServerError => e
49
+ raise AuthError, "Vault server error during JWT auth: #{e.message}"
50
+ end
51
+
52
+ # Authenticate and set the Vault client token for subsequent operations.
53
+ # This replaces the current Vault token with the JWT-authenticated one.
54
+ #
55
+ # @return [Hash] Same as login
56
+ def self.login!(jwt:, role: DEFAULT_ROLE, auth_path: DEFAULT_AUTH_PATH)
57
+ result = login(jwt: jwt, role: role, auth_path: auth_path)
58
+ ::Vault.token = result[:token]
59
+ Legion::Logging.info "[crypt:vault_jwt] authenticated via JWT auth, policies=#{result[:policies].join(',')}"
60
+ result
61
+ end
62
+
63
+ # Issue a Legion JWT and use it to authenticate to Vault in one step.
64
+ # Convenience method for workers that need Vault access.
65
+ #
66
+ # @param worker_id [String] Digital worker ID
67
+ # @param owner_msid [String] Worker's owner MSID
68
+ # @param role [String] Vault JWT auth role name
69
+ # @return [Hash] Same as login
70
+ def self.worker_login(worker_id:, owner_msid:, role: DEFAULT_ROLE)
71
+ jwt = Legion::Crypt::JWT.issue(
72
+ { worker_id: worker_id, sub: owner_msid, scope: 'vault', aud: 'legion' },
73
+ signing_key: Legion::Crypt.cluster_secret,
74
+ ttl: 300,
75
+ issuer: 'legion'
76
+ )
77
+
78
+ login(jwt: jwt, role: role)
79
+ end
80
+
81
+ def self.vault_connected?
82
+ defined?(::Vault) &&
83
+ defined?(Legion::Settings) &&
84
+ Legion::Settings[:crypt][:vault][:connected] == true
85
+ rescue StandardError
86
+ false
87
+ end
88
+
89
+ private_class_method :vault_connected?
90
+ end
91
+ end
92
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'legion/extensions/actors/every'
2
4
 
3
5
  module Legion
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Crypt
5
- VERSION = '0.3.0'
5
+ VERSION = '1.3.0'
6
6
  end
7
7
  end