legion-settings 1.3.0 → 1.3.1

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: b2fb957d328761484c232467b3f37dfea86359a31726a6f3910e2e02838e3977
4
- data.tar.gz: f10dcf26575430c481b466ebf490e3331d2d1c8ba841b438ca15560c8554c965
3
+ metadata.gz: cb0e2c44578e9e5b6cbb495c5160e1a37c067ea5692979e50e89494d33da9888
4
+ data.tar.gz: ebca4fdb1fb58e2fa315620209ccbe5f5302057a7dc0d4a256f9689ea8d5d8b0
5
5
  SHA512:
6
- metadata.gz: b69cabe6c5265040c40791eb9b0c9c284c42438b11b9c146113b57fa9dc6c87d4c28072a3ce569cf9a5f34d3151bac4adaf8ed0a923fd8cecbcf40b8aae6103c
7
- data.tar.gz: cbeaf56feeaca1cb4a02fdf76cf501e82d5619d489228570ec0906033e437249e3d85e98db652072c73b145bb80162f66f206dc6e1b0c79266606b7e647bf922
6
+ metadata.gz: 738f2ced6bf58fcb2d9ad4fca307d63abea4241bd6c5e788ff3ee47346f8f1054fd1cceb94d86e2f393f7985a0a835076513b94de730a89e9e233d923d5c1775
7
+ data.tar.gz: ac60c369c84bd0dcb53b84f5fcdf9440fcd25ea00d15bb32386b67a4b999ceead7dde040fa66de53c9e6b06a5e99628b674923d0503dcbbe945a5b30a7d88bb6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Legion::Settings Changelog
2
2
 
3
+ ## [1.3.1] - 2026-03-16
4
+
5
+ ### Added
6
+ - `lease://name#key` URI scheme in secret resolver for dynamic Vault leases
7
+ - Delegates to `Legion::Crypt::LeaseManager` for lease data lookup
8
+ - Registers reverse references for push-back on credential rotation
9
+
3
10
  ## [1.3.0] - 2026-03-16
4
11
 
5
12
  ### Added
data/CLAUDE.md CHANGED
@@ -77,7 +77,7 @@ Legion::Settings (singleton module)
77
77
  | `spec/legion/settings/validation_error_spec.rb` | Error formatting tests |
78
78
  | `spec/legion/settings/integration_spec.rb` | End-to-end validation tests |
79
79
  | `spec/legion/settings/role_defaults_spec.rb` | Role profile default settings tests |
80
- | `spec/legion/settings/resolver_spec.rb` | Secret resolver tests (env://, vault://, fallback chains) |
80
+ | `spec/legion/settings/resolver_spec.rb` | Secret resolver tests (env://, vault://, lease://, fallback chains) |
81
81
 
82
82
  ## Secret Resolution
83
83
 
@@ -87,8 +87,9 @@ Settings values can reference external secret sources using URI syntax. Resolved
87
87
 
88
88
  | Scheme | Format | Resolution |
89
89
  |--------|--------|------------|
90
- | `vault://` | `vault://path/to/secret#key` | `Legion::Crypt.read(path)[key]` |
90
+ | `vault://` | `vault://path/to/secret#key` | `Legion::Crypt.read(path)[key]` (static KV secrets) |
91
91
  | `env://` | `env://ENV_VAR_NAME` | `ENV['ENV_VAR_NAME']` |
92
+ | `lease://` | `lease://name#key` | `Legion::Crypt::LeaseManager.instance.fetch(name, key)` (dynamic Vault leases) |
92
93
  | *(plain string)* | `"guest"` | Returned as-is |
93
94
 
94
95
  ### Fallback Chains
data/README.md CHANGED
@@ -28,11 +28,40 @@ Legion::Settings[:transport][:connection][:host]
28
28
  ### Config Paths (checked in order)
29
29
 
30
30
  1. `/etc/legionio/`
31
- 2. `~/legionio/`
32
- 3. `./settings/`
31
+ 2. `~/.legionio/settings/`
32
+ 3. `~/legionio/`
33
+ 4. `./settings/`
33
34
 
34
35
  Each Legion module registers its own defaults via `merge_settings` during startup.
35
36
 
37
+ ### Secret Resolution
38
+
39
+ Settings values can reference external secret sources using URI syntax. Two schemes are supported:
40
+
41
+ | Scheme | Format | Resolution |
42
+ |--------|--------|------------|
43
+ | `vault://` | `vault://path/to/secret#key` | Reads from HashiCorp Vault via `Legion::Crypt` |
44
+ | `env://` | `env://ENV_VAR_NAME` | Reads from environment variable |
45
+
46
+ Array values act as fallback chains — the first non-nil result wins:
47
+
48
+ ```json
49
+ {
50
+ "transport": {
51
+ "connection": {
52
+ "password": ["vault://secret/data/rabbitmq#password", "env://RABBITMQ_PASSWORD", "guest"]
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ Call `Legion::Settings.resolve_secrets!` to resolve all URIs in-place. In the LegionIO boot sequence this is called automatically after `Legion::Crypt.start`. The `env://` scheme works even when Vault is not connected.
59
+
60
+ ```ruby
61
+ Legion::Settings.resolve_secrets!
62
+ # All vault:// and env:// references are now replaced with their resolved values
63
+ ```
64
+
36
65
  ### Schema Validation
37
66
 
38
67
  Types are inferred automatically from default values. Optional constraints can be added:
@@ -3,9 +3,10 @@
3
3
  module Legion
4
4
  module Settings
5
5
  module Resolver
6
- VAULT_PATTERN = %r{\Avault://(.+?)#(.+)\z}
7
- ENV_PATTERN = %r{\Aenv://(.+)\z}
8
- URI_PATTERN = %r{\A(?:vault|env)://}
6
+ VAULT_PATTERN = %r{\Avault://(.+?)#(.+)\z}
7
+ ENV_PATTERN = %r{\Aenv://(.+)\z}
8
+ LEASE_PATTERN = %r{\Alease://(.+?)#(.+)\z}
9
+ URI_PATTERN = %r{\A(?:vault|env|lease)://}
9
10
 
10
11
  module_function
11
12
 
@@ -18,6 +19,9 @@ module Legion
18
19
  vault_count = count_vault_refs(settings_hash)
19
20
  log_warn("Vault not connected — #{vault_count} vault:// reference(s) will not be resolved") if vault_count.positive? && !@vault_available
20
21
 
22
+ lease_count = count_lease_refs(settings_hash)
23
+ log_warn("LeaseManager not available — #{lease_count} lease:// reference(s) will not be resolved") if lease_count.positive? && !lease_manager_available?
24
+
21
25
  resolved = 0
22
26
  unresolved = 0
23
27
  walk(settings_hash, path: '') do |result|
@@ -51,6 +55,8 @@ module Legion
51
55
  def resolve_single(str)
52
56
  if (m = str.match(VAULT_PATTERN))
53
57
  resolve_vault(m[1], m[2])
58
+ elsif (m = str.match(LEASE_PATTERN))
59
+ resolve_lease(m[1], m[2])
54
60
  elsif (m = str.match(ENV_PATTERN))
55
61
  ENV.fetch(m[1], nil)
56
62
  else
@@ -112,6 +118,7 @@ module Legion
112
118
  block&.call(:unresolved)
113
119
  else
114
120
  hash[key] = resolved
121
+ register_lease_ref(value, current_path) if value.match?(LEASE_PATTERN)
115
122
  block&.call(:resolved)
116
123
  end
117
124
  when Array
@@ -123,6 +130,7 @@ module Legion
123
130
  block&.call(:unresolved)
124
131
  else
125
132
  hash[key] = resolved
133
+ register_lease_refs_from_chain(value, current_path)
126
134
  block&.call(:resolved)
127
135
  end
128
136
  end
@@ -145,10 +153,60 @@ module Legion
145
153
  data[key.to_sym] || data[key.to_s]
146
154
  end
147
155
 
156
+ def resolve_lease(name, key)
157
+ return nil unless lease_manager_available?
158
+
159
+ Legion::Crypt::LeaseManager.instance.fetch(name, key)
160
+ rescue StandardError => e
161
+ log_debug("Settings resolver: lease fetch failed for #{name}##{key}: #{e.message}")
162
+ nil
163
+ end
164
+
165
+ def lease_manager_available?
166
+ defined?(Legion::Crypt::LeaseManager)
167
+ rescue StandardError
168
+ false
169
+ end
170
+
148
171
  def resolvable_chain?(arr)
149
172
  arr.any? { |v| v.is_a?(String) && v.match?(URI_PATTERN) }
150
173
  end
151
174
 
175
+ def register_lease_ref(value, path_string)
176
+ return unless lease_manager_available?
177
+
178
+ m = value.match(LEASE_PATTERN)
179
+ return unless m
180
+
181
+ path_parts = path_string.split('.').map(&:to_sym)
182
+ Legion::Crypt::LeaseManager.instance.register_ref(m[1], m[2], path_parts)
183
+ rescue StandardError
184
+ nil
185
+ end
186
+
187
+ def register_lease_refs_from_chain(arr, path_string)
188
+ return unless lease_manager_available?
189
+
190
+ arr.each do |entry|
191
+ next unless entry.is_a?(String)
192
+
193
+ register_lease_ref(entry, path_string) if entry.match?(LEASE_PATTERN)
194
+ end
195
+ end
196
+
197
+ def count_lease_refs(hash)
198
+ return 0 unless hash.is_a?(Hash)
199
+
200
+ hash.sum do |_key, value|
201
+ case value
202
+ when String then value.match?(LEASE_PATTERN) ? 1 : 0
203
+ when Array then value.count { |v| v.is_a?(String) && v.match?(LEASE_PATTERN) }
204
+ when Hash then count_lease_refs(value)
205
+ else 0
206
+ end
207
+ end
208
+ end
209
+
152
210
  def log_info(message)
153
211
  if defined?(Legion::Logging)
154
212
  Legion::Logging.info(message)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Settings
5
- VERSION = '1.3.0'
5
+ VERSION = '1.3.1'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-settings
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity