secvault 2.7.1 → 3.0.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.
- checksums.yaml +4 -4
- data/README.md +389 -34
- data/lib/secvault/version.rb +1 -1
- data/lib/secvault.rb +102 -198
- 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: 7e9d55bf6f12233064ea7ced4e9b40c84f8f3a6ce38d6b5b79d5f0e8f4a7214a
|
|
4
|
+
data.tar.gz: 7ade516b0c550c0b3c98f04fc39ff1079d5c46436da6a7fd6153bdc1d9eac4fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b2939f3acd3e9525d000b7195fd9967a4e8f80c04800e4cbac9da774625bc1cae7b26e1f0320d4d57d53abbf1bdb53a3899977deb9625880fd84458c7c302277
|
|
7
|
+
data.tar.gz: 84de3b9f8ecd84e820668d3ac2bd7fedeb7946e2707f88e00cd7219f258d597417b025a72b889dc93bf6e408c2686b2c444b90e1d2eaee856f1040d9f5b8d63a
|
data/README.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
# Secvault
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Secvault restores the classic Rails `secrets.yml` functionality using simple, plain YAML files for environment-specific secrets management. Compatible with all modern Rails versions (7.1+, 7.2+, 8.0+).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://rubygems.org/gems/secvault)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **Rails
|
|
7
|
+
## Why Secvault?
|
|
8
|
+
|
|
9
|
+
- **Drop-in replacement** for Rails 7.2+'s removed `secrets.yml` functionality
|
|
10
|
+
- **Universal compatibility** across Rails 7.1+, 7.2+, and 8.0+
|
|
11
|
+
- **ERB templating** support for environment variables
|
|
12
|
+
- **Multi-file support** with deep merging capabilities
|
|
13
|
+
- **Shared sections** for common configuration across environments
|
|
14
|
+
- **Simple YAML** - no complex credential management required
|
|
10
15
|
|
|
11
16
|
## Installation
|
|
12
17
|
|
|
@@ -15,87 +20,437 @@ Restores Rails `secrets.yml` functionality for environment-specific secrets mana
|
|
|
15
20
|
gem 'secvault'
|
|
16
21
|
```
|
|
17
22
|
|
|
23
|
+
```bash
|
|
24
|
+
bundle install
|
|
25
|
+
```
|
|
26
|
+
|
|
18
27
|
## Quick Start
|
|
19
28
|
|
|
29
|
+
### 1. Simple Setup
|
|
30
|
+
|
|
31
|
+
Create `config/initializers/secvault.rb`:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
# The simplest setup - works across all Rails versions
|
|
35
|
+
Secvault.start!
|
|
36
|
+
```
|
|
37
|
+
|
|
20
38
|
Create `config/secrets.yml`:
|
|
21
39
|
|
|
22
40
|
```yaml
|
|
41
|
+
shared:
|
|
42
|
+
app_name: "My Rails App"
|
|
43
|
+
timeout: 30
|
|
44
|
+
|
|
23
45
|
development:
|
|
46
|
+
secret_key_base: "dev_secret_key_here"
|
|
24
47
|
api_key: "dev_key_123"
|
|
25
48
|
database_url: "postgresql://localhost/myapp_dev"
|
|
49
|
+
debug: true
|
|
50
|
+
|
|
51
|
+
test:
|
|
52
|
+
secret_key_base: "test_secret_key_here"
|
|
53
|
+
api_key: "test_key_123"
|
|
26
54
|
|
|
27
55
|
production:
|
|
56
|
+
secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
|
|
28
57
|
api_key: <%= ENV['API_KEY'] %>
|
|
29
58
|
database_url: <%= ENV['DATABASE_URL'] %>
|
|
59
|
+
debug: false
|
|
30
60
|
```
|
|
31
61
|
|
|
32
|
-
|
|
62
|
+
### 2. Access Your Secrets
|
|
33
63
|
|
|
34
64
|
```ruby
|
|
65
|
+
# In your Rails application
|
|
35
66
|
Rails.application.secrets.api_key
|
|
67
|
+
Rails.application.secrets.app_name
|
|
36
68
|
Rails.application.secrets.database_url
|
|
69
|
+
|
|
70
|
+
# Nested secrets work too
|
|
71
|
+
Rails.application.secrets.database.host
|
|
72
|
+
Rails.application.secrets.features.analytics
|
|
37
73
|
```
|
|
38
74
|
|
|
39
|
-
##
|
|
75
|
+
## Setup Methods
|
|
76
|
+
|
|
77
|
+
### Unified start! Method
|
|
40
78
|
|
|
41
|
-
|
|
79
|
+
Secvault now uses a single, simplified `start!` method for all use cases:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
# Simplest setup - loads config/secrets.yml with Rails integration
|
|
83
|
+
Secvault.start!
|
|
84
|
+
|
|
85
|
+
# Custom single file
|
|
86
|
+
Secvault.start!(files: ['custom.yml'])
|
|
87
|
+
|
|
88
|
+
# Multiple files with hot reload
|
|
89
|
+
Secvault.start!(
|
|
90
|
+
files: ['config/secrets.yml', 'config/secrets.local.yml'],
|
|
91
|
+
hot_reload: true
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# All available options
|
|
95
|
+
Secvault.start!(
|
|
96
|
+
files: ['config/secrets.yml'], # Default: ['config/secrets.yml']
|
|
97
|
+
integrate_with_rails: true, # Default: true
|
|
98
|
+
set_secret_key_base: true, # Default: true
|
|
99
|
+
hot_reload: true, # Default: true in development
|
|
100
|
+
logger: true # Default: true except production
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Advanced Usage
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
# Load without Rails integration (standalone mode)
|
|
108
|
+
Secvault.start!(integrate_with_rails: false)
|
|
109
|
+
# Access via: Secvault.secrets.your_key
|
|
110
|
+
|
|
111
|
+
# Multi-file with hot reload for development
|
|
112
|
+
Secvault.start!(
|
|
113
|
+
files: [
|
|
114
|
+
'config/secrets.yml',
|
|
115
|
+
'config/secrets.oauth.yml',
|
|
116
|
+
'config/secrets.local.yml' # Git-ignored local overrides
|
|
117
|
+
],
|
|
118
|
+
hot_reload: true,
|
|
119
|
+
logger: true
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
## Advanced Features
|
|
125
|
+
|
|
126
|
+
### ERB Templating
|
|
127
|
+
|
|
128
|
+
Secvault supports full ERB templating for dynamic configuration:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
production:
|
|
132
|
+
secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
|
|
133
|
+
api_key: <%= ENV['API_KEY'] %>
|
|
134
|
+
pool_size: <%= ENV.fetch('DB_POOL', '5').to_i %>
|
|
135
|
+
|
|
136
|
+
# Complex expressions
|
|
137
|
+
features:
|
|
138
|
+
enabled: <%= ENV.fetch('FEATURES_ON', 'false') == 'true' %>
|
|
139
|
+
analytics: <%= Rails.env.production? && ENV['ANALYTICS'] != 'false' %>
|
|
140
|
+
|
|
141
|
+
# Arrays and complex data structures
|
|
142
|
+
allowed_hosts: <%= ENV.fetch('ALLOWED_HOSTS', 'localhost').split(',') %>
|
|
143
|
+
|
|
144
|
+
# Conditional values
|
|
145
|
+
redis_url: <%=
|
|
146
|
+
if ENV['REDIS_URL']
|
|
147
|
+
ENV['REDIS_URL']
|
|
148
|
+
else
|
|
149
|
+
"redis://localhost:6379/#{Rails.env}"
|
|
150
|
+
end
|
|
151
|
+
%>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Shared Sections
|
|
155
|
+
|
|
156
|
+
Define common secrets that apply to all environments:
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
shared:
|
|
160
|
+
app_name: "MyApp"
|
|
161
|
+
version: "2.1.0"
|
|
162
|
+
timeout: 30
|
|
163
|
+
features:
|
|
164
|
+
analytics: true
|
|
165
|
+
|
|
166
|
+
development:
|
|
167
|
+
secret_key_base: "dev_secret"
|
|
168
|
+
features:
|
|
169
|
+
debug: true # Merges with shared.features
|
|
170
|
+
|
|
171
|
+
production:
|
|
172
|
+
secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
|
|
173
|
+
features:
|
|
174
|
+
analytics: false # Overrides shared.features.analytics
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Multi-File Configuration
|
|
178
|
+
|
|
179
|
+
Organize your secrets across multiple files for better maintainability:
|
|
42
180
|
|
|
43
181
|
```ruby
|
|
44
|
-
# config/initializers/secvault.rb
|
|
45
182
|
Secvault.setup_multi_file!([
|
|
46
|
-
'config/secrets.yml',
|
|
47
|
-
'config/secrets.oauth.yml',
|
|
48
|
-
'config/secrets.
|
|
183
|
+
'config/secrets.yml', # Base secrets
|
|
184
|
+
'config/secrets.oauth.yml', # OAuth provider settings
|
|
185
|
+
'config/secrets.database.yml', # Database configurations
|
|
186
|
+
'config/secrets.local.yml' # Local overrides (git-ignored)
|
|
49
187
|
])
|
|
50
188
|
```
|
|
51
189
|
|
|
52
|
-
|
|
190
|
+
**File merging behavior:**
|
|
191
|
+
- Files are processed in order
|
|
192
|
+
- Later files override earlier ones
|
|
193
|
+
- Deep merging for nested hashes
|
|
194
|
+
- Shared sections are merged first, then environment-specific
|
|
195
|
+
|
|
196
|
+
### Hot Reload (Development)
|
|
197
|
+
|
|
198
|
+
Secvault provides hot reload functionality for development:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
# Enable hot reload when starting Secvault
|
|
202
|
+
Secvault.start!(hot_reload: true) # Default: true in development
|
|
203
|
+
|
|
204
|
+
# Then reload secrets without restarting Rails
|
|
205
|
+
reload_secrets!
|
|
206
|
+
|
|
207
|
+
# Or via Rails.application
|
|
208
|
+
Rails.application.reload_secrets!
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Hot reload is automatically enabled in development mode and provides instant feedback when you change your secrets files.
|
|
53
212
|
|
|
54
213
|
## Manual API
|
|
55
214
|
|
|
215
|
+
For advanced use cases, you can use the lower-level API:
|
|
216
|
+
|
|
56
217
|
```ruby
|
|
57
218
|
# Parse specific files
|
|
58
|
-
secrets = Rails::Secrets.parse(['config/secrets.yml'], env:
|
|
219
|
+
secrets = Rails::Secrets.parse(['config/secrets.yml'], env: 'production')
|
|
220
|
+
|
|
221
|
+
# Load from default location
|
|
222
|
+
secrets = Rails::Secrets.load(env: 'development')
|
|
223
|
+
|
|
224
|
+
# Check if Secvault is active
|
|
225
|
+
Secvault.active? # => true/false
|
|
226
|
+
|
|
227
|
+
# Check if integrated with Rails
|
|
228
|
+
Secvault.rails_integrated? # => true/false
|
|
229
|
+
|
|
230
|
+
# Access loaded secrets directly
|
|
231
|
+
Secvault.secrets.api_key # Available after Secvault.start!
|
|
232
|
+
```
|
|
59
233
|
|
|
60
|
-
|
|
61
|
-
secrets = Rails::Secrets.load(env: 'production')
|
|
234
|
+
## Rails Version Compatibility
|
|
62
235
|
|
|
63
|
-
|
|
64
|
-
|
|
236
|
+
| Rails Version | Support Level | Notes |
|
|
237
|
+
|---------------|---------------|-------|
|
|
238
|
+
| **Rails 7.1+** | ✅ Full compatibility | Manual setup required |
|
|
239
|
+
| **Rails 7.2+** | ✅ Drop-in replacement | Automatic setup works |
|
|
240
|
+
| **Rails 8.0+** | ✅ Full compatibility | Future-proof |
|
|
241
|
+
|
|
242
|
+
### Rails 7.2+ Notes
|
|
243
|
+
Rails 7.2 removed the built-in `secrets.yml` functionality. Secvault provides a complete replacement with the same API.
|
|
244
|
+
|
|
245
|
+
### Rails 7.1 Notes
|
|
246
|
+
Rails 7.1 still has `secrets.yml` support but shows deprecation warnings. Secvault provides a consistent experience across Rails versions.
|
|
247
|
+
|
|
248
|
+
## Migration Guide
|
|
249
|
+
|
|
250
|
+
### From Previous Secvault Versions
|
|
251
|
+
|
|
252
|
+
**BREAKING CHANGE**: The API has been simplified. Update your initializers:
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
# Old API (no longer supported):
|
|
256
|
+
Secvault.setup!
|
|
257
|
+
Secvault.setup_multi_file!(['file1.yml', 'file2.yml'])
|
|
258
|
+
Secvault.integrate_with_rails!
|
|
259
|
+
|
|
260
|
+
# New unified API:
|
|
261
|
+
Secvault.start! # Simple case
|
|
262
|
+
Secvault.start!(files: ['file1.yml', 'file2.yml']) # Multi-file case
|
|
263
|
+
Secvault.start!(integrate_with_rails: false) # Standalone mode
|
|
65
264
|
```
|
|
66
265
|
|
|
67
|
-
|
|
266
|
+
### From Rails < 7.2 Built-in Secrets
|
|
267
|
+
|
|
268
|
+
1. **Add Secvault to your Gemfile**:
|
|
269
|
+
```ruby
|
|
270
|
+
gem 'secvault'
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
2. **Create initializer**:
|
|
274
|
+
```ruby
|
|
275
|
+
# config/initializers/secvault.rb
|
|
276
|
+
Secvault.start!
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
3. **Your existing `config/secrets.yml` works as-is** - no changes needed!
|
|
280
|
+
|
|
281
|
+
### From Rails Credentials
|
|
282
|
+
|
|
283
|
+
1. **Extract your credentials to YAML**:
|
|
284
|
+
```bash
|
|
285
|
+
# Export existing credentials
|
|
286
|
+
rails credentials:show > config/secrets.yml
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
2. **Format as environment-specific YAML**:
|
|
290
|
+
```yaml
|
|
291
|
+
development:
|
|
292
|
+
secret_key_base: "your_dev_secret"
|
|
293
|
+
# ... other secrets
|
|
294
|
+
|
|
295
|
+
production:
|
|
296
|
+
secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
|
|
297
|
+
# ... other secrets
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
3. **Set up Secvault**:
|
|
301
|
+
```ruby
|
|
302
|
+
# config/initializers/secvault.rb
|
|
303
|
+
Secvault.start!
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Configuration Examples
|
|
68
307
|
|
|
69
|
-
|
|
308
|
+
### Basic Application
|
|
309
|
+
|
|
310
|
+
```yaml
|
|
311
|
+
# config/secrets.yml
|
|
312
|
+
shared:
|
|
313
|
+
app_name: "MyApp"
|
|
314
|
+
|
|
315
|
+
development:
|
|
316
|
+
secret_key_base: "long_random_string_for_dev"
|
|
317
|
+
database_url: "postgresql://localhost/myapp_dev"
|
|
318
|
+
|
|
319
|
+
test:
|
|
320
|
+
secret_key_base: "long_random_string_for_test"
|
|
321
|
+
database_url: "postgresql://localhost/myapp_test"
|
|
322
|
+
|
|
323
|
+
production:
|
|
324
|
+
secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
|
|
325
|
+
database_url: <%= ENV['DATABASE_URL'] %>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Multi-Service Application
|
|
70
329
|
|
|
71
330
|
```ruby
|
|
72
331
|
# config/initializers/secvault.rb
|
|
73
|
-
Secvault.
|
|
332
|
+
Secvault.start!(
|
|
333
|
+
files: [
|
|
334
|
+
'config/secrets.yml',
|
|
335
|
+
'config/secrets.oauth.yml',
|
|
336
|
+
'config/secrets.external_apis.yml',
|
|
337
|
+
'config/secrets.local.yml' # Git-ignored
|
|
338
|
+
],
|
|
339
|
+
hot_reload: true
|
|
340
|
+
)
|
|
74
341
|
```
|
|
75
342
|
|
|
76
|
-
|
|
343
|
+
```yaml
|
|
344
|
+
# config/secrets.yml (base)
|
|
345
|
+
shared:
|
|
346
|
+
app_name: "MyApp"
|
|
347
|
+
timeout: 30
|
|
348
|
+
|
|
349
|
+
development:
|
|
350
|
+
secret_key_base: "dev_secret"
|
|
351
|
+
debug: true
|
|
77
352
|
|
|
78
|
-
|
|
353
|
+
production:
|
|
354
|
+
secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
|
|
355
|
+
debug: false
|
|
356
|
+
```
|
|
79
357
|
|
|
80
358
|
```yaml
|
|
359
|
+
# config/secrets.oauth.yml
|
|
360
|
+
shared:
|
|
361
|
+
oauth:
|
|
362
|
+
google:
|
|
363
|
+
scope: "email profile"
|
|
364
|
+
|
|
365
|
+
development:
|
|
366
|
+
oauth:
|
|
367
|
+
google:
|
|
368
|
+
client_id: "dev_google_client_id"
|
|
369
|
+
client_secret: "dev_google_client_secret"
|
|
370
|
+
|
|
81
371
|
production:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
hosts: <%= ENV.fetch('ALLOWED_HOSTS', 'localhost').split(',') %>
|
|
372
|
+
oauth:
|
|
373
|
+
google:
|
|
374
|
+
client_id: <%= ENV['GOOGLE_CLIENT_ID'] %>
|
|
375
|
+
client_secret: <%= ENV['GOOGLE_CLIENT_SECRET'] %>
|
|
87
376
|
```
|
|
88
377
|
|
|
89
|
-
##
|
|
378
|
+
## Troubleshooting
|
|
90
379
|
|
|
91
|
-
|
|
380
|
+
### Common Issues
|
|
92
381
|
|
|
382
|
+
**1. "No secrets.yml file found"**
|
|
383
|
+
```bash
|
|
384
|
+
# Create the file
|
|
385
|
+
mkdir -p config
|
|
386
|
+
touch config/secrets.yml
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**2. "undefined method `secrets' for Rails.application"**
|
|
93
390
|
```ruby
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
391
|
+
# Make sure Secvault is set up in an initializer
|
|
392
|
+
# config/initializers/secvault.rb
|
|
393
|
+
Secvault.start!
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**3. "Secrets not loading in tests"**
|
|
397
|
+
```ruby
|
|
398
|
+
# In your test helper or rails_helper.rb
|
|
399
|
+
Secvault.start! if defined?(Secvault)
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**4. "Environment variables not working"**
|
|
403
|
+
```yaml
|
|
404
|
+
# Make sure you're using ERB syntax
|
|
405
|
+
production:
|
|
406
|
+
api_key: <%= ENV['API_KEY'] %> # ✅ Correct
|
|
407
|
+
api_key: $API_KEY # ❌ Wrong
|
|
97
408
|
```
|
|
98
409
|
|
|
410
|
+
### Debug Mode
|
|
411
|
+
|
|
412
|
+
```ruby
|
|
413
|
+
# Enable detailed logging (development/test only)
|
|
414
|
+
Secvault.start!(files: ['config/secrets.yml'], logger: true)
|
|
415
|
+
|
|
416
|
+
# Check if Secvault is working
|
|
417
|
+
Secvault.active? # Should return true
|
|
418
|
+
Secvault.rails_integrated? # Should return true
|
|
419
|
+
Rails.application.secrets # Should show your secrets
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## API Reference
|
|
423
|
+
|
|
424
|
+
### Setup Methods
|
|
425
|
+
|
|
426
|
+
- `Secvault.start!(files: [], integrate_with_rails: true, set_secret_key_base: true, hot_reload: auto, logger: auto)` - Main and only setup method
|
|
427
|
+
|
|
428
|
+
### Status Methods
|
|
429
|
+
|
|
430
|
+
- `Secvault.active?` - Check if secrets are loaded
|
|
431
|
+
- `Secvault.rails_integrated?` - Check if Rails integration is active
|
|
432
|
+
- `Secvault.secrets` - Access loaded secrets directly
|
|
433
|
+
|
|
434
|
+
### Rails API Compatibility
|
|
435
|
+
|
|
436
|
+
- `Rails::Secrets.parse(files, env:)` - Parse specific files
|
|
437
|
+
- `Rails::Secrets.load(env:)` - Load from default config/secrets.yml
|
|
438
|
+
- `Rails.application.secrets` - Access secrets (same as classic Rails)
|
|
439
|
+
|
|
440
|
+
### Legacy Aliases
|
|
441
|
+
|
|
442
|
+
- `Secvault.setup_backward_compatibility_with_older_rails!` (alias for `setup!`)
|
|
443
|
+
- `Secvault.setup_rails_71_integration!` (alias for `setup!`)
|
|
444
|
+
- `Secvault.setup_multi_files!` (alias for `setup_multi_file!`)
|
|
445
|
+
|
|
446
|
+
## Contributing
|
|
447
|
+
|
|
448
|
+
1. Fork it
|
|
449
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
450
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
451
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
452
|
+
5. Create new Pull Request
|
|
453
|
+
|
|
99
454
|
## License
|
|
100
455
|
|
|
101
|
-
MIT
|
|
456
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
data/lib/secvault/version.rb
CHANGED
data/lib/secvault.rb
CHANGED
|
@@ -15,7 +15,7 @@ loader.setup
|
|
|
15
15
|
#
|
|
16
16
|
# Secvault restores the classic Rails secrets.yml functionality using simple,
|
|
17
17
|
# plain YAML files for environment-specific secrets management. Works consistently
|
|
18
|
-
# across all Rails versions
|
|
18
|
+
# across all Rails versions.
|
|
19
19
|
#
|
|
20
20
|
# ## Rails Version Support:
|
|
21
21
|
# - Rails 7.1+: Full compatibility with automatic setup
|
|
@@ -26,18 +26,20 @@ loader.setup
|
|
|
26
26
|
# Add this to an initializer:
|
|
27
27
|
#
|
|
28
28
|
# # config/initializers/secvault.rb
|
|
29
|
-
# Secvault.
|
|
29
|
+
# Secvault.start!
|
|
30
30
|
#
|
|
31
31
|
# ## Usage:
|
|
32
32
|
# Rails.application.secrets.api_key
|
|
33
33
|
# Rails.application.secrets.oauth_settings[:google_client_id]
|
|
34
|
+
# Secvault.secrets.your_key # Direct access
|
|
34
35
|
# Rails::Secrets.load(env: 'development') # Load default config/secrets.yml
|
|
35
36
|
# Rails::Secrets.parse(['custom.yml'], env: Rails.env) # Parse custom files
|
|
36
37
|
#
|
|
37
38
|
# ## Getting Started:
|
|
38
39
|
# 1. Create config/secrets.yml with your secrets
|
|
39
|
-
# 2.
|
|
40
|
-
# 3.
|
|
40
|
+
# 2. Call Secvault.start! in an initializer
|
|
41
|
+
# 3. Use Rails.application.secrets.your_secret in your app
|
|
42
|
+
# 4. For production, use environment variables with ERB syntax
|
|
41
43
|
#
|
|
42
44
|
# @see https://github.com/unnitallman/secvault
|
|
43
45
|
module Secvault
|
|
@@ -70,125 +72,78 @@ module Secvault
|
|
|
70
72
|
require "secvault/rails_secrets"
|
|
71
73
|
end
|
|
72
74
|
|
|
73
|
-
#
|
|
74
|
-
# This
|
|
75
|
-
# with consistent behavior across all Rails versions.
|
|
75
|
+
# Start Secvault with simplified, unified API
|
|
76
|
+
# This is the main entry point for all Secvault functionality
|
|
76
77
|
#
|
|
77
|
-
# Usage
|
|
78
|
-
# Secvault.
|
|
79
|
-
# Secvault.
|
|
78
|
+
# Usage examples:
|
|
79
|
+
# Secvault.start! # Simple: config/secrets.yml + Rails integration
|
|
80
|
+
# Secvault.start!(files: ['custom.yml']) # Custom single file
|
|
81
|
+
# Secvault.start!(files: ['base.yml', 'local.yml']) # Multiple files
|
|
82
|
+
# Secvault.start!(integrate_with_rails: false) # Load only, no Rails integration
|
|
83
|
+
# Secvault.start!(hot_reload: true) # Enable hot reload in development
|
|
80
84
|
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
# 3. Load secrets from config/secrets.yml automatically
|
|
85
|
-
# 4. Suppress Rails deprecation warnings about secrets (default: true)
|
|
86
|
-
# 5. Set Rails.application.config.secret_key_base from secrets (default: true)
|
|
87
|
-
def setup!(suppress_warnings: true, set_secret_key_base: true)
|
|
88
|
-
# Override native Rails::Secrets
|
|
89
|
-
Rails.send(:remove_const, :Secrets) if defined?(Rails::Secrets)
|
|
90
|
-
Rails.const_set(:Secrets, Secvault::RailsSecrets)
|
|
91
|
-
|
|
92
|
-
# Set up Rails.application.secrets replacement
|
|
93
|
-
Rails.application.config.after_initialize do
|
|
94
|
-
# Suppress Rails deprecation warnings about secrets if requested
|
|
95
|
-
suppress_secrets_deprecation_warning! if suppress_warnings
|
|
96
|
-
|
|
97
|
-
secrets_path = Rails.root.join("config/secrets.yml")
|
|
98
|
-
|
|
99
|
-
if secrets_path.exist?
|
|
100
|
-
# Load secrets using Secvault
|
|
101
|
-
loaded_secrets = Rails::Secrets.parse([secrets_path], env: Rails.env)
|
|
102
|
-
|
|
103
|
-
# Create ActiveSupport::OrderedOptions object for compatibility
|
|
104
|
-
secrets_object = ActiveSupport::OrderedOptions.new
|
|
105
|
-
secrets_object.merge!(loaded_secrets)
|
|
106
|
-
|
|
107
|
-
# Replace Rails.application.secrets
|
|
108
|
-
Rails.application.define_singleton_method(:secrets) do
|
|
109
|
-
secrets_object
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Set secret_key_base in Rails config to avoid accessing it from secrets
|
|
113
|
-
if set_secret_key_base && loaded_secrets.key?("secret_key_base")
|
|
114
|
-
Rails.application.config.secret_key_base = loaded_secrets["secret_key_base"]
|
|
115
|
-
unless Rails.env.production?
|
|
116
|
-
Rails.logger&.info "[Secvault] Set Rails.application.config.secret_key_base from secrets.yml"
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Log integration success (except in production)
|
|
121
|
-
unless Rails.env.production?
|
|
122
|
-
Rails.logger&.info "[Secvault] Integration complete. Loaded #{loaded_secrets.keys.size} secret keys."
|
|
123
|
-
end
|
|
124
|
-
else
|
|
125
|
-
Rails.logger&.warn "[Secvault] No secrets.yml file found at #{secrets_path}"
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Set up multi-file secrets loading with a clean API
|
|
131
|
-
# Just pass an array of file paths and Secvault handles the rest
|
|
132
|
-
#
|
|
133
|
-
# Usage in an initializer:
|
|
134
|
-
# Secvault.setup_multi_file!([
|
|
135
|
-
# 'config/secrets.yml',
|
|
136
|
-
# 'config/secrets.oauth.yml',
|
|
137
|
-
# 'config/secrets.local.yml'
|
|
138
|
-
# ])
|
|
85
|
+
# Access secrets:
|
|
86
|
+
# Rails.application.secrets.your_key # When integrate_rails: true (default)
|
|
87
|
+
# Secvault.secrets.your_key # Direct access (always available)
|
|
139
88
|
#
|
|
140
89
|
# Options:
|
|
141
|
-
# - files: Array of file paths (String or Pathname)
|
|
142
|
-
# -
|
|
143
|
-
# - logger: Enable/disable logging (default: true except in production)
|
|
144
|
-
# - suppress_warnings: Suppress Rails deprecation warnings about secrets (default: true)
|
|
90
|
+
# - files: Array of file paths (String or Pathname). Defaults to ['config/secrets.yml']
|
|
91
|
+
# - integrate_with_rails: Integrate with Rails.application.secrets (default: true)
|
|
145
92
|
# - set_secret_key_base: Set Rails.application.config.secret_key_base from secrets (default: true)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
93
|
+
# - hot_reload: Add reload_secrets! methods for development (default: true in development)
|
|
94
|
+
# - logger: Enable logging (default: true except production)
|
|
95
|
+
def start!(files: [], integrate_with_rails: true, set_secret_key_base: true,
|
|
96
|
+
hot_reload: (defined?(Rails) && Rails.env.respond_to?(:development?) ? Rails.env.development? : false),
|
|
97
|
+
logger: (defined?(Rails) && Rails.env.respond_to?(:production?) ? !Rails.env.production? : true))
|
|
98
|
+
|
|
99
|
+
# Default to config/secrets.yml if no files specified
|
|
100
|
+
files_to_load = files.empty? ? ["config/secrets.yml"] : Array(files)
|
|
101
|
+
|
|
102
|
+
# Convert to Pathname objects and resolve relative to Rails.root
|
|
103
|
+
file_paths = files_to_load.map do |file|
|
|
153
104
|
file.is_a?(Pathname) ? file : Rails.root.join(file)
|
|
154
105
|
end
|
|
155
|
-
|
|
156
|
-
#
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
106
|
+
|
|
107
|
+
# Load secrets into Secvault.secrets
|
|
108
|
+
load_secrets!(file_paths, logger: logger)
|
|
109
|
+
|
|
110
|
+
# Integrate with Rails if requested
|
|
111
|
+
if integrate_with_rails
|
|
112
|
+
setup_rails_integration!(file_paths, set_secret_key_base: set_secret_key_base, logger: logger)
|
|
160
113
|
end
|
|
161
|
-
|
|
162
|
-
# Add reload
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
114
|
+
|
|
115
|
+
# Add hot reload functionality if requested
|
|
116
|
+
if hot_reload
|
|
117
|
+
add_hot_reload!(file_paths)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
true
|
|
121
|
+
rescue => e
|
|
122
|
+
Rails.logger&.error "[Secvault] Failed to start: #{e.message}" if defined?(Rails) && logger
|
|
123
|
+
false
|
|
166
124
|
end
|
|
167
125
|
|
|
168
|
-
|
|
169
|
-
def load_secrets_only!(files, logger: !Rails.env.production?)
|
|
170
|
-
# Convert strings to Pathname objects and resolve relative to Rails.root
|
|
171
|
-
file_paths = Array(files).map do |file|
|
|
172
|
-
file.is_a?(Pathname) ? file : Rails.root.join(file)
|
|
173
|
-
end
|
|
126
|
+
private
|
|
174
127
|
|
|
128
|
+
# Load secrets into Secvault.secrets (internal storage)
|
|
129
|
+
def load_secrets!(file_paths, logger: (defined?(Rails) && Rails.env.respond_to?(:production?) ? !Rails.env.production? : true))
|
|
175
130
|
existing_files = file_paths.select(&:exist?)
|
|
176
|
-
|
|
131
|
+
|
|
177
132
|
if existing_files.any?
|
|
178
|
-
# Load and merge all secrets files
|
|
133
|
+
# Load and merge all secrets files
|
|
179
134
|
merged_secrets = Secvault::Secrets.parse(existing_files, env: Rails.env)
|
|
180
|
-
|
|
181
|
-
# Store in
|
|
135
|
+
|
|
136
|
+
# Store in internal storage with ActiveSupport::OrderedOptions for compatibility
|
|
182
137
|
@@loaded_secrets = ActiveSupport::OrderedOptions.new
|
|
183
138
|
@@loaded_secrets.merge!(merged_secrets)
|
|
184
|
-
|
|
139
|
+
|
|
185
140
|
# Log successful loading
|
|
186
141
|
if logger
|
|
187
142
|
file_names = existing_files.map(&:basename)
|
|
188
143
|
Rails.logger&.info "[Secvault] Loaded #{existing_files.size} files: #{file_names.join(", ")}"
|
|
189
144
|
Rails.logger&.info "[Secvault] Parsed #{merged_secrets.keys.size} secret keys for #{Rails.env}"
|
|
190
145
|
end
|
|
191
|
-
|
|
146
|
+
|
|
192
147
|
true
|
|
193
148
|
else
|
|
194
149
|
Rails.logger&.warn "[Secvault] No secrets files found" if logger
|
|
@@ -196,116 +151,65 @@ module Secvault
|
|
|
196
151
|
false
|
|
197
152
|
end
|
|
198
153
|
end
|
|
199
|
-
|
|
200
|
-
#
|
|
201
|
-
def
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if logger
|
|
227
|
-
file_names = existing_files.map(&:basename)
|
|
228
|
-
Rails.logger&.info "[Secvault Multi-File] Loaded #{existing_files.size} files: #{file_names.join(", ")}"
|
|
229
|
-
Rails.logger&.info "[Secvault Multi-File] Merged #{merged_secrets.keys.size} secret keys for #{Rails.env}"
|
|
154
|
+
|
|
155
|
+
# Set up Rails integration
|
|
156
|
+
def setup_rails_integration!(file_paths, set_secret_key_base: true, logger: (defined?(Rails) && Rails.env.respond_to?(:production?) ? !Rails.env.production? : true))
|
|
157
|
+
# Override native Rails::Secrets with Secvault implementation
|
|
158
|
+
Rails.send(:remove_const, :Secrets) if defined?(Rails::Secrets)
|
|
159
|
+
Rails.const_set(:Secrets, Secvault::RailsSecrets)
|
|
160
|
+
|
|
161
|
+
# Set up Rails.application.secrets replacement in after_initialize
|
|
162
|
+
Rails.application.config.after_initialize do
|
|
163
|
+
if @@loaded_secrets && !@@loaded_secrets.empty?
|
|
164
|
+
# Replace Rails.application.secrets with our loaded secrets
|
|
165
|
+
Rails.application.define_singleton_method(:secrets) do
|
|
166
|
+
@@loaded_secrets
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Set secret_key_base in Rails config to avoid accessing it from secrets
|
|
170
|
+
if set_secret_key_base && @@loaded_secrets.key?(:secret_key_base)
|
|
171
|
+
Rails.application.config.secret_key_base = @@loaded_secrets[:secret_key_base]
|
|
172
|
+
Rails.logger&.info "[Secvault] Set Rails.application.config.secret_key_base from secrets" if logger
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Log integration success (except in production)
|
|
176
|
+
if logger
|
|
177
|
+
Rails.logger&.info "[Secvault] Rails integration complete. #{@@loaded_secrets.keys.size} secret keys available."
|
|
178
|
+
end
|
|
179
|
+
else
|
|
180
|
+
Rails.logger&.warn "[Secvault] No secrets loaded for Rails integration" if logger
|
|
230
181
|
end
|
|
231
|
-
|
|
232
|
-
merged_secrets
|
|
233
|
-
else
|
|
234
|
-
Rails.logger&.warn "[Secvault Multi-File] No secrets files found" if logger
|
|
235
|
-
{}
|
|
236
182
|
end
|
|
237
183
|
end
|
|
238
|
-
|
|
239
|
-
# Add reload
|
|
240
|
-
def
|
|
184
|
+
|
|
185
|
+
# Add hot reload functionality for development
|
|
186
|
+
def add_hot_reload!(file_paths)
|
|
241
187
|
# Define reload method on Rails.application
|
|
242
188
|
Rails.application.define_singleton_method(:reload_secrets!) do
|
|
243
|
-
|
|
244
|
-
|
|
189
|
+
# Reload secrets
|
|
190
|
+
Secvault.send(:load_secrets!, file_paths, logger: true)
|
|
191
|
+
|
|
192
|
+
# Re-apply Rails integration if needed
|
|
193
|
+
if Secvault.rails_integrated? && @@loaded_secrets
|
|
194
|
+
Rails.application.define_singleton_method(:secrets) do
|
|
195
|
+
@@loaded_secrets
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
puts "🔄 Hot reloaded secrets from #{file_paths.size} files"
|
|
245
200
|
true
|
|
246
201
|
end
|
|
247
|
-
|
|
202
|
+
|
|
248
203
|
# Also make it available as a top-level method
|
|
249
204
|
Object.define_method(:reload_secrets!) do
|
|
250
205
|
Rails.application.reload_secrets!
|
|
251
206
|
end
|
|
207
|
+
|
|
208
|
+
Rails.logger&.info "[Secvault] Hot reload enabled. Use reload_secrets! to refresh secrets." unless (defined?(Rails) && Rails.env.respond_to?(:production?) && Rails.env.production?)
|
|
252
209
|
end
|
|
210
|
+
|
|
211
|
+
public
|
|
253
212
|
|
|
254
|
-
# Start Secvault and load secrets (without Rails integration)
|
|
255
|
-
#
|
|
256
|
-
# Usage:
|
|
257
|
-
# Secvault.start! # Uses config/secrets.yml only
|
|
258
|
-
# Secvault.start!(files: []) # Same as above
|
|
259
|
-
# Secvault.start!(files: ['path/to/secrets.yml']) # Custom single file
|
|
260
|
-
# Secvault.start!(files: ['gem.yml', 'app.yml']) # Multiple files
|
|
261
|
-
#
|
|
262
|
-
# Access loaded secrets via: Secvault.secrets.your_key
|
|
263
|
-
# To integrate with Rails.application.secrets, call: Secvault.integrate_with_rails!
|
|
264
|
-
#
|
|
265
|
-
# Options:
|
|
266
|
-
# - files: Array of file paths (String or Pathname). Defaults to ['config/secrets.yml']
|
|
267
|
-
# - logger: Enable logging (default: true except production)
|
|
268
|
-
def start!(files: [], logger: !Rails.env.production?)
|
|
269
|
-
# Default to host app's config/secrets.yml if no files specified
|
|
270
|
-
files_to_load = files.empty? ? ["config/secrets.yml"] : files
|
|
271
|
-
|
|
272
|
-
# Load secrets into Secvault.secrets (completely independent of Rails)
|
|
273
|
-
load_secrets_only!(files_to_load, logger: logger)
|
|
274
|
-
|
|
275
|
-
true
|
|
276
|
-
rescue => e
|
|
277
|
-
Rails.logger&.error "[Secvault] Failed to start: #{e.message}" if defined?(Rails)
|
|
278
|
-
false
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
# Integrate loaded secrets with Rails.application.secrets
|
|
282
|
-
def integrate_with_rails!
|
|
283
|
-
return false unless @@loaded_secrets
|
|
284
|
-
|
|
285
|
-
begin
|
|
286
|
-
# Set up Rails::Secrets to use Secvault's parser (only when integrating)
|
|
287
|
-
unless rails_integrated?
|
|
288
|
-
Rails.send(:remove_const, :Secrets) if defined?(Rails::Secrets)
|
|
289
|
-
Rails.const_set(:Secrets, Secvault::RailsSecrets)
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
# Replace Rails.application.secrets with Secvault's loaded secrets
|
|
293
|
-
Rails.application.define_singleton_method(:secrets) do
|
|
294
|
-
Secvault.secrets
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
Rails.logger&.info "[Secvault] Integrated with Rails.application.secrets" unless Rails.env.production?
|
|
298
|
-
true
|
|
299
|
-
rescue => e
|
|
300
|
-
Rails.logger&.error "[Secvault] Failed to integrate with Rails: #{e.message}" if defined?(Rails)
|
|
301
|
-
false
|
|
302
|
-
end
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
# Backward compatibility aliases
|
|
306
|
-
alias_method :setup_backward_compatibility_with_older_rails!, :setup! # Legacy name
|
|
307
|
-
alias_method :setup_rails_71_integration!, :setup! # Legacy name
|
|
308
|
-
alias_method :setup_multi_files!, :setup_multi_file! # Alternative name
|
|
309
213
|
end
|
|
310
214
|
|
|
311
215
|
Secvault.install! if defined?(Rails)
|