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 +4 -4
- data/CHANGELOG.md +7 -0
- data/CLAUDE.md +3 -2
- data/README.md +31 -2
- data/lib/legion/settings/resolver.rb +61 -3
- data/lib/legion/settings/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cb0e2c44578e9e5b6cbb495c5160e1a37c067ea5692979e50e89494d33da9888
|
|
4
|
+
data.tar.gz: ebca4fdb1fb58e2fa315620209ccbe5f5302057a7dc0d4a256f9689ea8d5d8b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
32
|
-
3.
|
|
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
|
|
7
|
-
ENV_PATTERN
|
|
8
|
-
|
|
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)
|