legion-crypt 1.5.38 → 1.5.42

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +30 -3
  3. data/.rspec +2 -0
  4. data/AGENTS.md +2 -0
  5. data/CHANGELOG.md +28 -0
  6. data/CLAUDE.md +64 -192
  7. data/CODEOWNERS +4 -4
  8. data/Gemfile +4 -1
  9. data/README.md +60 -16
  10. data/legion-crypt.gemspec +1 -0
  11. data/lib/legion/crypt/cert_rotation.rb +7 -21
  12. data/lib/legion/crypt/cluster_secret.rb +3 -14
  13. data/lib/legion/crypt/ed25519.rb +2 -10
  14. data/lib/legion/crypt/erasure.rb +3 -13
  15. data/lib/legion/crypt/jwks_client.rb +4 -4
  16. data/lib/legion/crypt/kerberos_auth.rb +2 -2
  17. data/lib/legion/crypt/ldap_auth.rb +2 -2
  18. data/lib/legion/crypt/lease_manager.rb +7 -7
  19. data/lib/legion/crypt/mtls.rb +8 -46
  20. data/lib/legion/crypt/partition_keys.rb +2 -6
  21. data/lib/legion/crypt/settings.rb +24 -21
  22. data/lib/legion/crypt/spiffe/svid_rotation.rb +6 -9
  23. data/lib/legion/crypt/spiffe.rb +14 -39
  24. data/lib/legion/crypt/token_renewer.rb +6 -6
  25. data/lib/legion/crypt/vault/client.rb +355 -0
  26. data/lib/legion/crypt/vault/cluster.rb +330 -0
  27. data/lib/legion/crypt/vault/entity.rb +80 -0
  28. data/lib/legion/crypt/vault/jwt_auth.rb +99 -0
  29. data/lib/legion/crypt/vault/kerberos_auth.rb +53 -0
  30. data/lib/legion/crypt/vault/operation.rb +29 -0
  31. data/lib/legion/crypt/vault.rb +6 -11
  32. data/lib/legion/crypt/version.rb +1 -1
  33. data/lib/legion/crypt.rb +69 -80
  34. metadata +23 -8
  35. data/lib/legion/crypt/vault_client.rb +0 -355
  36. data/lib/legion/crypt/vault_cluster.rb +0 -331
  37. data/lib/legion/crypt/vault_entity.rb +0 -78
  38. data/lib/legion/crypt/vault_jwt_auth.rb +0 -97
  39. data/lib/legion/crypt/vault_kerberos_auth.rb +0 -51
  40. data/lib/legion/crypt/vault_operation.rb +0 -27
  41. /data/lib/legion/crypt/{vault_renewer.rb → vault/renewer.rb} +0 -0
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/logging/helper'
4
- require 'legion/crypt/vault_operation'
4
+ require 'legion/crypt/vault/operation'
5
5
 
6
6
  module Legion
7
7
  module Crypt
@@ -53,8 +53,8 @@ module Legion
53
53
  @wake.broadcast
54
54
  @thread
55
55
  end
56
- deadline ||= ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + Legion::Crypt::VaultOperation::SHUTDOWN_TIMEOUT
57
- Legion::Crypt::VaultOperation.with_timeout(deadline: deadline) { thread.join } if thread && thread != Thread.current
56
+ deadline ||= ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + Legion::Crypt::Vault::Operation::SHUTDOWN_TIMEOUT
57
+ Legion::Crypt::Vault::Operation.with_timeout(deadline: deadline) { thread.join } if thread && thread != Thread.current
58
58
  @mutex.synchronize { @thread = nil if @thread.equal?(thread) && !thread&.alive? }
59
59
  log.info('[mTLS] CertRotation stopped')
60
60
  rescue StandardError => e
@@ -67,7 +67,7 @@ module Legion
67
67
 
68
68
  def rotate!(generation: nil)
69
69
  generation ||= @mutex.synchronize { @generation }
70
- new_cert = Legion::Crypt::VaultOperation.with_timeout { Legion::Crypt::Mtls.issue_cert(common_name: node_common_name) }
70
+ new_cert = Legion::Crypt::Vault::Operation.with_timeout { Legion::Crypt::Mtls.issue_cert(common_name: node_common_name) }
71
71
  issued_at = Time.now
72
72
  raise 'Vault PKI returned an expired certificate' unless new_cert[:expiry] > issued_at
73
73
 
@@ -133,31 +133,17 @@ module Legion
133
133
  end
134
134
 
135
135
  def renewal_window
136
- return 0.5 unless defined?(Legion::Settings)
137
-
138
- security = Legion::Settings[:security]
139
- return 0.5 if security.nil?
140
-
141
- mtls = security[:mtls] || security['mtls'] || {}
142
- window = mtls[:renewal_window] || mtls['renewal_window'] || 0.5
136
+ window = Legion::Settings[:crypt][:mtls][:renewal_window]
143
137
  unless window.is_a?(Numeric) && window.finite? && window >= 0 && window < 1
144
138
  raise ArgumentError, 'Certificate renewal window must be finite and between zero and one (exclusive)'
145
139
  end
146
140
 
147
141
  window
148
- rescue StandardError => e
149
- handle_exception(e, level: :debug, operation: 'crypt.cert_rotation.renewal_window')
150
- 0.5
151
142
  end
152
143
 
153
144
  def node_common_name
154
- return 'legion.internal' unless defined?(Legion::Settings)
155
-
156
- name = Legion::Settings[:client]&.dig(:name) || Legion::Settings[:client]&.dig('name')
157
- name || 'legion.internal'
158
- rescue StandardError => e
159
- handle_exception(e, level: :debug, operation: 'crypt.cert_rotation.node_common_name')
160
- 'legion.internal'
145
+ name = Legion::Settings.dig(:client, :name)
146
+ name.nil? ? 'legion.internal' : name
161
147
  end
162
148
 
163
149
  def emit_rotated_event(cert)
@@ -97,17 +97,6 @@ module Legion
97
97
  nil
98
98
  end
99
99
 
100
- def force_cluster_secret
101
- Legion::Settings[:crypt].fetch(:force_cluster_secret, false)
102
- end
103
-
104
- def settings_push_vault
105
- vault_settings = Legion::Settings[:crypt][:vault]
106
- return vault_settings[:push_cluster_secret] unless vault_settings[:push_cluster_secret].nil?
107
-
108
- vault_settings.fetch(:push_cs_to_vault, false)
109
- end
110
-
111
100
  def only_member?
112
101
  return true if defined?(Legion::Transport::TYPE) && Legion::Transport::TYPE == 'local'
113
102
 
@@ -130,7 +119,7 @@ module Legion
130
119
  Legion::Settings[:crypt][:cluster_secret] = value.dup.freeze
131
120
  @cluster_secret_cache = nil
132
121
  Legion::Settings[:crypt][:cs_encrypt_ready] = true
133
- push_cs_to_vault if push_to_vault && settings_push_vault
122
+ push_cs_to_vault if push_to_vault && Legion::Settings[:crypt][:vault][:push_cluster_secret]
134
123
  log.info "Cluster secret loaded into settings push_to_vault=#{push_to_vault}"
135
124
  end
136
125
 
@@ -145,7 +134,7 @@ module Legion
145
134
  end
146
135
 
147
136
  def cluster_secret_timeout
148
- timeout = Legion::Settings[:crypt].fetch(:cluster_secret_timeout, 5)
137
+ timeout = Legion::Settings[:crypt][:cluster_secret_timeout]
149
138
  unless timeout.is_a?(Numeric) && timeout.finite? && timeout.positive?
150
139
  raise InitializationError, 'cluster_secret_timeout must be a finite positive number'
151
140
  end
@@ -154,7 +143,7 @@ module Legion
154
143
  end
155
144
 
156
145
  def secret_length
157
- length = Legion::Settings[:crypt][:cluster_length] || Legion::Settings[:crypt][:cluster_lenth] || 32
146
+ length = Legion::Settings[:crypt][:cluster_length]
158
147
  raise InitializationError, 'cluster_length must be a positive integer' unless length.is_a?(Integer) && length.positive?
159
148
 
160
149
  length
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'ed25519'
4
4
  require 'legion/logging/helper'
5
+ require 'legion/crypt/settings'
5
6
 
6
7
  module Legion
7
8
  module Crypt
@@ -83,17 +84,8 @@ module Legion
83
84
 
84
85
  private
85
86
 
86
- def key_prefix
87
- begin
88
- Legion::Settings[:crypt][:ed25519][:vault_key_prefix]
89
- rescue StandardError => e
90
- handle_exception(e, level: :debug, operation: 'crypt.ed25519.key_prefix')
91
- nil
92
- end || 'keys'
93
- end
94
-
95
87
  def vault_key_path(agent_id)
96
- normalize_kv_path("#{key_prefix}/#{agent_id}")
88
+ normalize_kv_path("#{Legion::Settings[:crypt][:ed25519][:vault_key_prefix]}/#{agent_id}")
97
89
  end
98
90
 
99
91
  def normalize_kv_path(path)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/logging/helper'
4
+ require 'legion/crypt/settings'
4
5
 
5
6
  module Legion
6
7
  module Crypt
@@ -9,7 +10,7 @@ module Legion
9
10
 
10
11
  class << self
11
12
  def erase_tenant(tenant_id:)
12
- key_path = "#{tenant_prefix}/#{tenant_id}/master_key"
13
+ key_path = "#{Legion::Settings[:crypt][:partition_keys][:vault_tenant_prefix]}/#{tenant_id}/master_key"
13
14
 
14
15
  log.info "[crypt] Erasing tenant #{tenant_id}"
15
16
  result = Legion::Crypt.delete(key_path)
@@ -25,7 +26,7 @@ module Legion
25
26
  end
26
27
 
27
28
  def verify_erasure(tenant_id:)
28
- key_path = "#{tenant_prefix}/#{tenant_id}/master_key"
29
+ key_path = "#{Legion::Settings[:crypt][:partition_keys][:vault_tenant_prefix]}/#{tenant_id}/master_key"
29
30
 
30
31
  data = Legion::Crypt.read(key_path, nil)
31
32
  erased = data.nil?
@@ -35,17 +36,6 @@ module Legion
35
36
  handle_exception(e, level: :warn, operation: 'crypt.erasure.verify_erasure', tenant_id: tenant_id)
36
37
  { erased: false, tenant_id: tenant_id, error: e.message }
37
38
  end
38
-
39
- private
40
-
41
- def tenant_prefix
42
- begin
43
- Legion::Settings[:crypt][:partition_keys][:vault_tenant_prefix]
44
- rescue StandardError => e
45
- handle_exception(e, level: :debug, operation: 'crypt.erasure.tenant_prefix')
46
- nil
47
- end || 'secret/data/legion/tenants'
48
- end
49
39
  end
50
40
  end
51
41
  end
@@ -6,6 +6,7 @@ require 'json'
6
6
  require 'openssl'
7
7
  require 'jwt'
8
8
  require 'legion/logging/helper'
9
+ require 'legion/crypt/settings'
9
10
  require 'concurrent'
10
11
 
11
12
  module Legion
@@ -172,12 +173,11 @@ module Legion
172
173
  end
173
174
 
174
175
  def jwks_ssl_verify_mode
175
- return OpenSSL::SSL::VERIFY_PEER unless defined?(Legion::Settings)
176
-
177
176
  verify = Legion::Settings[:crypt][:jwt][:jwks_tls_verify]&.to_s
178
177
  verify == 'none' ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
179
- rescue StandardError
180
- OpenSSL::SSL::VERIFY_PEER
178
+ rescue StandardError => e
179
+ handle_exception(e, level: :warn, operation: 'crypt.jwks.ssl_verify_mode')
180
+ raise
181
181
  end
182
182
 
183
183
  def with_url_lock(jwks_url, &)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/logging/helper'
4
- require 'legion/crypt/vault_client'
4
+ require 'legion/crypt/vault/client'
5
5
 
6
6
  module Legion
7
7
  module Crypt
@@ -94,7 +94,7 @@ module Legion
94
94
  body: '{}',
95
95
  headers: { 'Authorization' => "Negotiate #{spnego_token}" }
96
96
  )
97
- response = Legion::Crypt::VaultClient::Secret.decode(data: json)
97
+ response = Legion::Crypt::Vault::Client::Secret.decode(data: json)
98
98
  raise AuthError, 'Vault Kerberos auth returned no auth data' unless response&.auth
99
99
 
100
100
  auth = response.auth
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/logging/helper'
4
- require 'legion/crypt/vault_operation'
4
+ require 'legion/crypt/vault/operation'
5
5
 
6
6
  module Legion
7
7
  module Crypt
@@ -14,7 +14,7 @@ module Legion
14
14
  config = clusters.fetch(cluster_name)
15
15
  log.info "LDAP login requested user=#{username} cluster=#{cluster_name}"
16
16
  client = vault_client(cluster_name)
17
- secret = Legion::Crypt::VaultOperation.with_timeout do
17
+ secret = Legion::Crypt::Vault::Operation.with_timeout do
18
18
  client.logical.write(path: "auth/ldap/login/#{username}", data: { password: password })
19
19
  end
20
20
  auth = secret.auth
@@ -3,7 +3,7 @@
3
3
  require 'legion/logging/helper'
4
4
  require 'singleton'
5
5
  require 'timeout'
6
- require 'legion/crypt/vault_operation'
6
+ require 'legion/crypt/vault/operation'
7
7
 
8
8
  module Legion
9
9
  module Crypt
@@ -189,7 +189,7 @@ module Legion
189
189
 
190
190
  def start_renewal_thread
191
191
  previous = @state_mutex.synchronize { @renewal_thread if @renewal_generation != @generation }
192
- Legion::Crypt::VaultOperation.with_timeout { previous.join } if previous&.alive?
192
+ Legion::Crypt::Vault::Operation.with_timeout { previous.join } if previous&.alive?
193
193
  @state_mutex.synchronize do
194
194
  return if @renewal_thread&.alive? || @stopped
195
195
  return if Legion::Crypt.respond_to?(:stopping) && Legion::Crypt.stopping
@@ -218,7 +218,7 @@ module Legion
218
218
  end
219
219
 
220
220
  def shutdown(deadline: nil)
221
- deadline ||= monotonic_now + Legion::Crypt::VaultOperation::SHUTDOWN_TIMEOUT
221
+ deadline ||= monotonic_now + Legion::Crypt::Vault::Operation::SHUTDOWN_TIMEOUT
222
222
  request_stop
223
223
  @state_mutex.synchronize do
224
224
  @active_leases.clear
@@ -376,7 +376,7 @@ module Legion
376
376
 
377
377
  def with_lease_operation(name:, deadline: nil, **)
378
378
  lock = @state_mutex.synchronize { @lease_locks[name] ||= Mutex.new }
379
- Legion::Crypt::VaultOperation.with_timeout(deadline: deadline) do |operation_deadline|
379
+ Legion::Crypt::Vault::Operation.with_timeout(deadline: deadline) do |operation_deadline|
380
380
  lock.synchronize { yield operation_deadline }
381
381
  end
382
382
  end
@@ -510,7 +510,7 @@ module Legion
510
510
  safe_to_retire = !handoff[:attempted_reconnect] || !lifecycle_current?(generation: generation, active: active)
511
511
  if handoff[:attempted_reconnect] && restored
512
512
  begin
513
- safe_to_retire = Legion::Crypt::VaultOperation.with_timeout(deadline: context[:deadline]) do
513
+ safe_to_retire = Legion::Crypt::Vault::Operation.with_timeout(deadline: context[:deadline]) do
514
514
  trigger_reconnect(name, generation: generation, active: active)
515
515
  end
516
516
  rescue StandardError => e
@@ -602,7 +602,7 @@ module Legion
602
602
  end
603
603
  return unless entry
604
604
 
605
- Legion::Crypt::VaultOperation.with_timeout(deadline: deadline) { entry[:client].sys.revoke(lease_id: entry[:lease_id]) }
605
+ Legion::Crypt::Vault::Operation.with_timeout(deadline: deadline) { entry[:client].sys.revoke(lease_id: entry[:lease_id]) }
606
606
  @state_mutex.synchronize { @owned_leases.delete(key) if @owned_leases[key].equal?(entry) }
607
607
  rescue StandardError => e
608
608
  handle_exception(e, level: :warn, operation: 'crypt.lease_manager.revoke_owned_lease', lease_name: entry&.dig(:name))
@@ -620,7 +620,7 @@ module Legion
620
620
  thread = request_stop
621
621
  return unless thread
622
622
 
623
- Legion::Crypt::VaultOperation.with_timeout(deadline: deadline) { thread.join }
623
+ Legion::Crypt::Vault::Operation.with_timeout(deadline: deadline) { thread.join }
624
624
  @state_mutex.synchronize { @renewal_thread = nil if @renewal_thread.equal?(thread) }
625
625
  rescue StandardError => e
626
626
  handle_exception(e, level: :warn, operation: 'crypt.lease_manager.stop_renewal_thread')
@@ -1,47 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/logging/helper'
4
+ require 'legion/crypt/settings'
4
5
  require 'socket'
5
6
 
6
7
  module Legion
7
8
  module Crypt
8
9
  module Mtls
9
- DEFAULT_PKI_PATH = 'pki/issue/legion-internal'
10
- DEFAULT_TTL = '24h'
11
10
  extend Legion::Logging::Helper
12
11
 
13
12
  class << self
14
13
  def enabled?
15
- security = safe_security_settings
16
- return false if security.nil?
17
-
18
- mtls = security[:mtls] || security['mtls']
19
- return false if mtls.nil?
20
-
21
- mtls[:enabled] || mtls['enabled'] || false
22
- end
23
-
24
- def pki_path
25
- security = safe_security_settings
26
- return DEFAULT_PKI_PATH if security.nil?
27
-
28
- mtls = security[:mtls] || security['mtls'] || {}
29
- mtls[:vault_pki_path] || mtls['vault_pki_path'] || DEFAULT_PKI_PATH
14
+ Legion::Settings[:crypt][:mtls][:enabled] == true
30
15
  end
31
16
 
32
- def issue_cert(common_name:, ttl: nil)
33
- resolved_ttl = ttl || cert_ttl_setting || DEFAULT_TTL
17
+ def issue_cert(common_name:, alt_names: '', ttl: nil, **)
18
+ pki_path = Legion::Settings[:crypt][:mtls][:vault_pki_path]
19
+ resolved_ttl = ttl.nil? ? Legion::Settings[:crypt][:mtls][:cert_ttl] : ttl
34
20
  log.info "[mTLS] certificate issue requested common_name=#{common_name} ttl=#{resolved_ttl}"
35
21
 
36
22
  response = Legion::Crypt.vault_client.logical.write(
37
23
  path: pki_path,
38
- data: { common_name: common_name, ttl: resolved_ttl, ip_sans: local_ip, alt_names: '' }
24
+ data: { common_name: common_name, ttl: resolved_ttl, ip_sans: local_ip, alt_names: alt_names }
39
25
  )
40
-
41
26
  raise "Vault PKI returned nil for #{pki_path} (common_name=#{common_name})" if response.nil?
42
27
 
43
28
  data = response.data
44
-
45
29
  {
46
30
  cert: data[:certificate],
47
31
  key: data[:private_key],
@@ -54,34 +38,12 @@ module Legion
54
38
  raise
55
39
  end
56
40
 
57
- def local_ip
41
+ def local_ip(**)
58
42
  Socket.ip_address_list.find { |a| a.ipv4? && !a.ipv4_loopback? }&.ip_address || '127.0.0.1'
59
- rescue StandardError => e
43
+ rescue SocketError, SystemCallError, IOError => e
60
44
  handle_exception(e, level: :warn, operation: 'crypt.mtls.local_ip')
61
45
  '127.0.0.1'
62
46
  end
63
-
64
- private
65
-
66
- def safe_security_settings
67
- return nil unless defined?(Legion::Settings)
68
-
69
- Legion::Settings[:security]
70
- rescue StandardError => e
71
- handle_exception(e, level: :debug, operation: 'crypt.mtls.safe_security_settings')
72
- nil
73
- end
74
-
75
- def cert_ttl_setting
76
- security = safe_security_settings
77
- return nil if security.nil?
78
-
79
- mtls = security[:mtls] || security['mtls'] || {}
80
- mtls[:cert_ttl] || mtls['cert_ttl']
81
- rescue StandardError => e
82
- handle_exception(e, level: :debug, operation: 'crypt.mtls.cert_ttl_setting')
83
- nil
84
- end
85
47
  end
86
48
  end
87
49
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'openssl'
4
4
  require 'legion/logging/helper'
5
+ require 'legion/crypt/settings'
5
6
 
6
7
  module Legion
7
8
  module Crypt
@@ -10,12 +11,7 @@ module Legion
10
11
 
11
12
  class << self
12
13
  def derive_key(master_key:, tenant_id:, context: nil)
13
- context ||= begin
14
- Legion::Settings[:crypt][:partition_keys][:derivation_context]
15
- rescue StandardError => e
16
- handle_exception(e, level: :debug, operation: 'crypt.partition_keys.derivation_context', tenant_id: tenant_id)
17
- nil
18
- end || 'legion-partition'
14
+ context = Legion::Settings[:crypt][:partition_keys][:derivation_context] if context.nil?
19
15
  log.debug "PartitionKeys deriving key for tenant #{tenant_id}"
20
16
  salt = OpenSSL::Digest::SHA256.digest(tenant_id.to_s)
21
17
  OpenSSL::KDF.hkdf(master_key, salt: salt, info: context, length: 32, hash: 'SHA256')
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/logging/helper'
4
+ require 'legion/settings'
4
5
 
5
6
  module Legion
6
7
  module Crypt
@@ -17,6 +18,15 @@ module Legion
17
18
  }
18
19
  end
19
20
 
21
+ def self.mtls
22
+ {
23
+ enabled: false,
24
+ vault_pki_path: 'pki/issue/legion-internal',
25
+ cert_ttl: '24h',
26
+ renewal_window: 0.5
27
+ }
28
+ end
29
+
20
30
  def self.spiffe
21
31
  {
22
32
  enabled: false,
@@ -30,14 +40,19 @@ module Legion
30
40
 
31
41
  def self.default
32
42
  {
33
- vault: vault,
34
- jwt: jwt,
35
- tls: tls,
36
- cs_encrypt_ready: false,
37
- dynamic_keys: true,
38
- cluster_secret: nil,
39
- save_private_key: true,
40
- read_private_key: true
43
+ vault: vault,
44
+ jwt: jwt,
45
+ tls: tls,
46
+ mtls: mtls,
47
+ cs_encrypt_ready: false,
48
+ dynamic_keys: true,
49
+ cluster_secret: nil,
50
+ cluster_length: 32,
51
+ cluster_secret_timeout: 5,
52
+ save_private_key: true,
53
+ read_private_key: true,
54
+ ed25519: { vault_key_prefix: 'keys' },
55
+ partition_keys: { derivation_context: 'legion-partition', vault_tenant_prefix: 'secret/data/legion/tenants' }
41
56
  }
42
57
  end
43
58
 
@@ -87,16 +102,4 @@ module Legion
87
102
  end
88
103
  end
89
104
 
90
- begin
91
- if Legion.const_defined?('Settings')
92
- Legion::Settings.merge_settings('crypt', Legion::Crypt::Settings.default)
93
- Legion::Crypt::Settings.log.info('Legion::Crypt settings defaults merged')
94
- end
95
- rescue StandardError => e
96
- if Legion::Crypt::Settings.respond_to?(:handle_exception)
97
- Legion::Crypt::Settings.handle_exception(e, level: :fatal, operation: 'crypt.settings.merge_defaults')
98
- else
99
- puts e.message
100
- puts e.backtrace
101
- end
102
- end
105
+ Legion::Settings.register_library(:crypt, Legion::Crypt::Settings.default)
@@ -130,16 +130,13 @@ module Legion
130
130
  end
131
131
 
132
132
  def renewal_window
133
- return SVID_RENEWAL_RATIO unless defined?(Legion::Settings)
134
-
135
- security = Legion::Settings[:security]
136
- return SVID_RENEWAL_RATIO if security.nil?
133
+ window = Legion::Settings.dig(:security, :spiffe, :renewal_window)
134
+ window = SVID_RENEWAL_RATIO if window.nil?
135
+ unless window.is_a?(Numeric) && window.finite? && window >= 0 && window < 1
136
+ raise ArgumentError, 'SVID renewal window must be finite and between zero and one (exclusive)'
137
+ end
137
138
 
138
- spiffe = security[:spiffe] || security['spiffe'] || {}
139
- spiffe[:renewal_window] || spiffe['renewal_window'] || SVID_RENEWAL_RATIO
140
- rescue StandardError => e
141
- handle_exception(e, level: :debug, operation: 'crypt.spiffe.svid_rotation.renewal_window')
142
- SVID_RENEWAL_RATIO
139
+ window
143
140
  end
144
141
  end
145
142
  end
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'legion/logging/helper'
4
3
  require 'uri'
5
4
  require 'openssl'
5
+ require 'legion/logging/helper'
6
+ require 'legion/crypt/settings'
7
+ require 'legion/crypt/spiffe/workload_api_client'
8
+ require 'legion/crypt/spiffe/svid_rotation'
9
+ require 'legion/crypt/spiffe/identity_helpers'
6
10
 
7
11
  module Legion
8
12
  module Crypt
@@ -10,7 +14,7 @@ module Legion
10
14
  SPIFFE_SCHEME = 'spiffe'
11
15
  DEFAULT_SOCKET_PATH = '/tmp/spire-agent/public/api.sock'
12
16
  DEFAULT_TRUST_DOMAIN = 'legion.internal'
13
- SVID_RENEWAL_RATIO = 0.5
17
+ SVID_RENEWAL_RATIO = 0.5
14
18
 
15
19
  class Error < StandardError; end
16
20
  class InvalidSpiffeIdError < Error; end
@@ -80,50 +84,30 @@ module Legion
80
84
  parse_id(spiffe_id_string)
81
85
  true
82
86
  rescue InvalidSpiffeIdError => e
83
- handle_exception(e, level: :debug, operation: 'crypt.spiffe.valid_id', spiffe_id: spiffe_id_string)
87
+ handle_exception(e, level: :warn, operation: 'crypt.spiffe.valid_id', spiffe_id: spiffe_id_string)
84
88
  false
85
89
  end
86
90
 
87
91
  def enabled?
88
- security = safe_security_settings
89
- return false if security.nil?
90
-
91
- spiffe = security[:spiffe] || security['spiffe']
92
- return false if spiffe.nil?
93
-
94
- spiffe[:enabled] || spiffe['enabled'] || false
92
+ Legion::Settings.dig(:security, :spiffe, :enabled) == true
95
93
  end
96
94
 
97
95
  def socket_path
98
- security = safe_security_settings
99
- return DEFAULT_SOCKET_PATH if security.nil?
100
-
101
- spiffe = security[:spiffe] || security['spiffe'] || {}
102
- spiffe[:socket_path] || spiffe['socket_path'] || DEFAULT_SOCKET_PATH
96
+ path = Legion::Settings.dig(:security, :spiffe, :socket_path)
97
+ path.nil? ? DEFAULT_SOCKET_PATH : path
103
98
  end
104
99
 
105
100
  def trust_domain
106
- security = safe_security_settings
107
- return DEFAULT_TRUST_DOMAIN if security.nil?
108
-
109
- spiffe = security[:spiffe] || security['spiffe'] || {}
110
- spiffe[:trust_domain] || spiffe['trust_domain'] || DEFAULT_TRUST_DOMAIN
101
+ domain = Legion::Settings.dig(:security, :spiffe, :trust_domain)
102
+ domain.nil? ? DEFAULT_TRUST_DOMAIN : domain
111
103
  end
112
104
 
113
105
  def workload_id
114
- security = safe_security_settings
115
- return nil if security.nil?
116
-
117
- spiffe = security[:spiffe] || security['spiffe'] || {}
118
- spiffe[:workload_id] || spiffe['workload_id']
106
+ Legion::Settings.dig(:security, :spiffe, :workload_id)
119
107
  end
120
108
 
121
109
  def allow_x509_fallback?
122
- security = safe_security_settings
123
- return false if security.nil?
124
-
125
- spiffe = security[:spiffe] || security['spiffe'] || {}
126
- spiffe[:allow_x509_fallback] || spiffe['allow_x509_fallback'] || false
110
+ Legion::Settings.dig(:security, :spiffe, :allow_x509_fallback) == true
127
111
  end
128
112
 
129
113
  private
@@ -138,15 +122,6 @@ module Legion
138
122
 
139
123
  raise InvalidSpiffeIdError, "SPIFFE ID must not contain userinfo, port, query, or fragment in '#{raw}'"
140
124
  end
141
-
142
- def safe_security_settings
143
- return nil unless defined?(Legion::Settings)
144
-
145
- Legion::Settings[:security]
146
- rescue StandardError => e
147
- handle_exception(e, level: :debug, operation: 'crypt.spiffe.safe_security_settings')
148
- nil
149
- end
150
125
  end
151
126
  end
152
127
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  require 'legion/logging/helper'
4
4
  require 'legion/crypt/kerberos_auth'
5
- require 'legion/crypt/vault_operation'
6
- require 'legion/crypt/vault_client'
5
+ require 'legion/crypt/vault/operation'
6
+ require 'legion/crypt/vault/client'
7
7
 
8
8
  module Legion
9
9
  module Crypt
@@ -57,7 +57,7 @@ module Legion
57
57
  end
58
58
 
59
59
  def stop(deadline: nil)
60
- deadline ||= ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + VaultOperation::SHUTDOWN_TIMEOUT
60
+ deadline ||= ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + Legion::Crypt::Vault::Operation::SHUTDOWN_TIMEOUT
61
61
  @state_mutex.synchronize do
62
62
  return if @stopping
63
63
 
@@ -86,7 +86,7 @@ module Legion
86
86
  deadline = @state_mutex.synchronize do
87
87
  ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + (@expires_at - Time.now) if @expires_at
88
88
  end
89
- VaultOperation.with_timeout(deadline: deadline) do
89
+ Legion::Crypt::Vault::Operation.with_timeout(deadline: deadline) do
90
90
  result = @vault_client.auth_token.renew_self
91
91
  @state_mutex.synchronize do
92
92
  return false unless active_generation?(generation: generation)
@@ -133,7 +133,7 @@ module Legion
133
133
  true
134
134
  end
135
135
  end
136
- VaultOperation.with_timeout do
136
+ Legion::Crypt::Vault::Operation.with_timeout do
137
137
  result = Legion::Crypt::KerberosAuth.login(
138
138
  vault_client: @vault_client,
139
139
  service_principal: krb_config[:service_principal],
@@ -307,7 +307,7 @@ module Legion
307
307
  end
308
308
  return unless owned_token
309
309
 
310
- VaultOperation.with_timeout(deadline: deadline) { @vault_client.auth_token.revoke_self }
310
+ Legion::Crypt::Vault::Operation.with_timeout(deadline: deadline) { @vault_client.auth_token.revoke_self }
311
311
  log.info("TokenRenewer[#{@cluster_name}]: Vault token revoked")
312
312
  rescue StandardError => e
313
313
  handle_exception(e, level: :warn, operation: 'crypt.token_renewer.revoke_token', cluster_name: @cluster_name)