secvault 3.0.0 → 3.2.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 +45 -391
- data/SECURITY.md +98 -0
- data/lib/secvault/rails_secrets.rb +41 -38
- data/lib/secvault/railtie.rb +130 -10
- data/lib/secvault/secrets.rb +7 -7
- data/lib/secvault/version.rb +1 -1
- data/lib/secvault.rb +140 -32
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff511b4da4c277844438956507222da90bd15416fd8e4adec1e973a4e5b73993
|
4
|
+
data.tar.gz: 45219a2e917ac366af2c94c8a848cc5557219b4856daab6fcd870d19898d1685
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45e5d96b9eaa32ea9396921451dfb2ed148e485992236ae45bf58acf9a380869cde8df8b3e93e621cdfe1bcd9710f4eccbeaee3880fa5436c73e336d063d866f
|
7
|
+
data.tar.gz: db91a660ae62430a0c27f311bd512d964efc485f4a224e42f958b9215d31c9c553db765b638b622413b18c9b236ab6865d14328594f8d6ab726452484eddc767
|
data/README.md
CHANGED
@@ -1,456 +1,110 @@
|
|
1
1
|
# Secvault
|
2
2
|
|
3
|
-
|
3
|
+
Simple YAML secrets management for Rails. Uses standard YAML anchors for sharing configuration.
|
4
4
|
|
5
5
|
[](https://rubygems.org/gems/secvault)
|
6
|
-
|
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
|
6
|
+
[](https://github.com/unnitallman/secvault/actions/workflows/ci.yml)
|
15
7
|
|
16
8
|
## Installation
|
17
9
|
|
18
10
|
```ruby
|
19
|
-
# Gemfile
|
20
11
|
gem 'secvault'
|
21
12
|
```
|
22
13
|
|
23
|
-
|
24
|
-
bundle install
|
25
|
-
```
|
26
|
-
|
27
|
-
## Quick Start
|
28
|
-
|
29
|
-
### 1. Simple Setup
|
30
|
-
|
31
|
-
Create `config/initializers/secvault.rb`:
|
14
|
+
## Usage
|
32
15
|
|
16
|
+
**1. Add to initializer:**
|
33
17
|
```ruby
|
34
|
-
#
|
18
|
+
# config/initializers/secvault.rb
|
35
19
|
Secvault.start!
|
36
20
|
```
|
37
21
|
|
38
|
-
Create
|
39
|
-
|
22
|
+
**2. Create secrets file:**
|
40
23
|
```yaml
|
41
|
-
|
42
|
-
|
43
|
-
|
24
|
+
# config/secrets.yml
|
25
|
+
defaults: &defaults
|
26
|
+
app_name: "MyApp"
|
44
27
|
|
45
28
|
development:
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
debug: true
|
50
|
-
|
51
|
-
test:
|
52
|
-
secret_key_base: "test_secret_key_here"
|
53
|
-
api_key: "test_key_123"
|
29
|
+
<<: *defaults
|
30
|
+
secret_key_base: "dev_secret"
|
31
|
+
api_key: "dev_key"
|
54
32
|
|
55
33
|
production:
|
34
|
+
<<: *defaults
|
56
35
|
secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
|
57
36
|
api_key: <%= ENV['API_KEY'] %>
|
58
|
-
database_url: <%= ENV['DATABASE_URL'] %>
|
59
|
-
debug: false
|
60
|
-
```
|
61
|
-
|
62
|
-
### 2. Access Your Secrets
|
63
|
-
|
64
|
-
```ruby
|
65
|
-
# In your Rails application
|
66
|
-
Rails.application.secrets.api_key
|
67
|
-
Rails.application.secrets.app_name
|
68
|
-
Rails.application.secrets.database_url
|
69
|
-
|
70
|
-
# Nested secrets work too
|
71
|
-
Rails.application.secrets.database.host
|
72
|
-
Rails.application.secrets.features.analytics
|
73
37
|
```
|
74
38
|
|
75
|
-
|
76
|
-
|
77
|
-
### Unified start! Method
|
78
|
-
|
79
|
-
Secvault now uses a single, simplified `start!` method for all use cases:
|
80
|
-
|
39
|
+
**3. Use in your app:**
|
81
40
|
```ruby
|
82
|
-
|
83
|
-
Secvault.
|
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
|
-
)
|
41
|
+
Secvault.secrets.api_key
|
42
|
+
Secvault.secrets.app_name
|
102
43
|
```
|
103
44
|
|
104
|
-
|
45
|
+
## Options
|
105
46
|
|
106
47
|
```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
48
|
Secvault.start!(
|
113
|
-
files: [
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
hot_reload: true,
|
119
|
-
logger: true
|
49
|
+
files: ['config/secrets.yml'], # Files to load (later files override earlier ones)
|
50
|
+
integrate_with_rails: false, # Add Rails.application.secrets
|
51
|
+
set_secret_key_base: true, # Auto-set Rails.application.config.secret_key_base from secrets
|
52
|
+
hot_reload: true, # Auto-reload in development
|
53
|
+
logger: true # Log loading activity
|
120
54
|
)
|
121
55
|
```
|
122
56
|
|
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:
|
180
|
-
|
57
|
+
**Multiple files:**
|
181
58
|
```ruby
|
182
|
-
|
183
|
-
|
184
|
-
'config/secrets.oauth.yml', # OAuth provider settings
|
185
|
-
'config/secrets.database.yml', # Database configurations
|
186
|
-
'config/secrets.local.yml' # Local overrides (git-ignored)
|
187
|
-
])
|
59
|
+
# Later files override earlier ones
|
60
|
+
Secvault.start!(files: ['secrets.yml', 'local.yml'])
|
188
61
|
```
|
189
62
|
|
190
|
-
**
|
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
|
-
|
63
|
+
**Rails integration:**
|
200
64
|
```ruby
|
201
|
-
|
202
|
-
|
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.
|
212
|
-
|
213
|
-
## Manual API
|
214
|
-
|
215
|
-
For advanced use cases, you can use the lower-level API:
|
216
|
-
|
217
|
-
```ruby
|
218
|
-
# Parse specific files
|
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!
|
65
|
+
Secvault.start!(integrate_with_rails: true)
|
66
|
+
Rails.application.secrets.api_key # Now available
|
232
67
|
```
|
233
68
|
|
234
|
-
|
235
|
-
|
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
|
-
|
69
|
+
**Secret key base:**
|
254
70
|
```ruby
|
255
|
-
#
|
256
|
-
|
257
|
-
Secvault.
|
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
|
71
|
+
# If your secrets.yml has secret_key_base, it's automatically set
|
72
|
+
# This replaces the need for Rails.application.config.secret_key_base
|
73
|
+
Secvault.start!(set_secret_key_base: true) # Default behavior
|
264
74
|
```
|
265
75
|
|
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
76
|
|
306
|
-
##
|
307
|
-
|
308
|
-
### Basic Application
|
77
|
+
## Advanced
|
309
78
|
|
79
|
+
**ERB templating:**
|
310
80
|
```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
81
|
production:
|
324
|
-
|
325
|
-
|
326
|
-
```
|
327
|
-
|
328
|
-
### Multi-Service Application
|
329
|
-
|
330
|
-
```ruby
|
331
|
-
# config/initializers/secvault.rb
|
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
|
-
)
|
82
|
+
api_key: <%= ENV['API_KEY'] %>
|
83
|
+
pool_size: <%= ENV.fetch('DB_POOL', '5').to_i %>
|
341
84
|
```
|
342
85
|
|
86
|
+
**YAML anchors for sharing:**
|
343
87
|
```yaml
|
344
|
-
|
345
|
-
shared:
|
88
|
+
defaults: &defaults
|
346
89
|
app_name: "MyApp"
|
347
90
|
timeout: 30
|
348
91
|
|
349
92
|
development:
|
350
|
-
|
93
|
+
<<: *defaults
|
351
94
|
debug: true
|
352
95
|
|
353
96
|
production:
|
354
|
-
|
355
|
-
|
356
|
-
```
|
357
|
-
|
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
|
-
|
371
|
-
production:
|
372
|
-
oauth:
|
373
|
-
google:
|
374
|
-
client_id: <%= ENV['GOOGLE_CLIENT_ID'] %>
|
375
|
-
client_secret: <%= ENV['GOOGLE_CLIENT_SECRET'] %>
|
376
|
-
```
|
377
|
-
|
378
|
-
## Troubleshooting
|
379
|
-
|
380
|
-
### Common Issues
|
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"**
|
390
|
-
```ruby
|
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
|
+
<<: *defaults
|
98
|
+
timeout: 10 # Override specific values
|
408
99
|
```
|
409
100
|
|
410
|
-
|
411
|
-
|
101
|
+
**Development helpers:**
|
412
102
|
```ruby
|
413
|
-
#
|
414
|
-
Secvault.
|
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
|
103
|
+
reload_secrets! # Reload files
|
104
|
+
Secvault.active? # Check status
|
420
105
|
```
|
421
106
|
|
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
107
|
|
454
108
|
## License
|
455
109
|
|
456
|
-
MIT
|
110
|
+
MIT
|
data/SECURITY.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
## Supported Versions
|
4
|
+
|
5
|
+
We actively support the following versions of Secvault with security updates:
|
6
|
+
|
7
|
+
| Version | Supported |
|
8
|
+
| ------- | ------------------ |
|
9
|
+
| 3.1.x | :white_check_mark: |
|
10
|
+
| 3.0.x | :white_check_mark: |
|
11
|
+
| < 3.0 | :x: |
|
12
|
+
|
13
|
+
## Reporting a Vulnerability
|
14
|
+
|
15
|
+
**Please do not report security vulnerabilities through public GitHub issues.**
|
16
|
+
|
17
|
+
Instead, please report security vulnerabilities by emailing **unnikrishnan.kp@bigbinary.com**.
|
18
|
+
|
19
|
+
You should receive a response within 48 hours. If the issue is confirmed, we will release a patch as soon as possible depending on complexity but typically within 7 days.
|
20
|
+
|
21
|
+
### What to Include in Your Report
|
22
|
+
|
23
|
+
Please include the following information in your vulnerability report:
|
24
|
+
|
25
|
+
- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
26
|
+
- Full paths of source file(s) related to the manifestation of the issue
|
27
|
+
- The location of the affected source code (tag/branch/commit or direct URL)
|
28
|
+
- Any special configuration required to reproduce the issue
|
29
|
+
- Step-by-step instructions to reproduce the issue
|
30
|
+
- Proof-of-concept or exploit code (if possible)
|
31
|
+
- Impact of the issue, including how an attacker might exploit the issue
|
32
|
+
|
33
|
+
This information will help us triage your report more quickly.
|
34
|
+
|
35
|
+
## Security Best Practices
|
36
|
+
|
37
|
+
When using Secvault in your applications, please follow these security best practices:
|
38
|
+
|
39
|
+
### 1. File Permissions
|
40
|
+
- Ensure your secrets files (`config/secrets.yml`, etc.) have restrictive file permissions (600 or 640)
|
41
|
+
- Never commit secrets files to version control
|
42
|
+
- Use `.gitignore` to exclude secrets files from your repository
|
43
|
+
|
44
|
+
### 2. Environment Separation
|
45
|
+
- Use different secrets files for different environments (development, staging, production)
|
46
|
+
- Never use production secrets in development or testing environments
|
47
|
+
- Implement proper environment-specific configuration
|
48
|
+
|
49
|
+
### 3. Secret Management
|
50
|
+
- Rotate secrets regularly
|
51
|
+
- Use strong, randomly generated secrets
|
52
|
+
- Avoid hardcoding secrets in application code
|
53
|
+
- Consider using external secret management services for production environments
|
54
|
+
|
55
|
+
### 4. Access Control
|
56
|
+
- Limit access to secrets files to only necessary personnel and processes
|
57
|
+
- Use proper deployment practices that don't expose secrets in logs or process lists
|
58
|
+
- Implement proper access controls in your deployment infrastructure
|
59
|
+
|
60
|
+
### 5. Monitoring and Auditing
|
61
|
+
- Monitor access to secrets files
|
62
|
+
- Implement logging for secrets access (without logging the actual secret values)
|
63
|
+
- Regular security audits of your secrets management practices
|
64
|
+
|
65
|
+
## Dependencies and Supply Chain Security
|
66
|
+
|
67
|
+
Secvault has minimal dependencies to reduce attack surface:
|
68
|
+
|
69
|
+
- **Rails**: We require Rails >= 7.1.0 and stay updated with security patches
|
70
|
+
- **Zeitwerk**: Used for autoloading, maintained by the Rails core team
|
71
|
+
|
72
|
+
We regularly monitor our dependencies for security vulnerabilities and update them promptly when security issues are discovered.
|
73
|
+
|
74
|
+
## Security Considerations
|
75
|
+
|
76
|
+
### Hot Reload Feature
|
77
|
+
The hot reload feature (`reload_secrets!`) is designed for development environments only. It should not be enabled in production as it can potentially expose secrets through memory dumps or debugging tools.
|
78
|
+
|
79
|
+
### Rails Integration
|
80
|
+
Secvault integrates deeply with Rails' secrets system. While this provides seamless functionality, it's important to understand that secrets are loaded into memory and may be visible to processes with sufficient privileges.
|
81
|
+
|
82
|
+
### File System Security
|
83
|
+
Secvault reads secrets from the file system. Ensure your deployment environment has proper file system security controls in place.
|
84
|
+
|
85
|
+
## Acknowledgments
|
86
|
+
|
87
|
+
We appreciate the security research community and responsible disclosure. Contributors who report valid security vulnerabilities will be acknowledged in our release notes (unless they prefer to remain anonymous).
|
88
|
+
|
89
|
+
## Contact
|
90
|
+
|
91
|
+
For any security-related questions or concerns, please contact:
|
92
|
+
|
93
|
+
**Email**: unnikrishnan.kp@bigbinary.com
|
94
|
+
**Project**: https://github.com/unnitallman/secvault
|
95
|
+
|
96
|
+
---
|
97
|
+
|
98
|
+
*This security policy is effective as of the date of the latest commit to this file and applies to all current and future versions of Secvault.*
|
@@ -1,54 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Secvault
|
4
|
-
# Rails::Secrets compatibility
|
4
|
+
# Rails::Secrets compatibility class
|
5
5
|
# Provides the classic Rails::Secrets interface for backwards compatibility
|
6
|
-
# This replicates the Rails < 7.2 Rails::Secrets
|
7
|
-
|
8
|
-
|
6
|
+
# This replicates the Rails < 7.2 Rails::Secrets class functionality
|
7
|
+
class RailsSecrets
|
8
|
+
class << self
|
9
|
+
attr_accessor :root
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
11
|
+
# Parse secrets from one or more YAML files
|
12
|
+
#
|
13
|
+
# Supports:
|
14
|
+
# - ERB templating for environment variables
|
15
|
+
# - Environment-specific sections (YAML anchors handle sharing)
|
16
|
+
# - Multiple files (merged in order)
|
17
|
+
# - Deep symbolized keys
|
18
|
+
#
|
19
|
+
# Examples:
|
20
|
+
# # Single file
|
21
|
+
# Rails::Secrets.parse(['config/secrets.yml'], env: 'development')
|
22
|
+
#
|
23
|
+
# # Multiple files (merged in order)
|
24
|
+
# Rails::Secrets.parse([
|
25
|
+
# 'config/secrets.yml',
|
26
|
+
# 'config/secrets.local.yml'
|
27
|
+
# ], env: 'development')
|
28
|
+
#
|
29
|
+
# # Load default config/secrets.yml
|
30
|
+
# Rails::Secrets.load # uses current Rails.env
|
31
|
+
# Rails::Secrets.load(env: 'production')
|
32
|
+
def parse(paths, env:)
|
33
|
+
Secvault::Secrets.parse(paths, env: env.to_s)
|
34
|
+
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
# Load secrets from the default config/secrets.yml file
|
37
|
+
def load(env: Rails.env)
|
38
|
+
secrets_path = Rails.root.join("config/secrets.yml")
|
39
|
+
parse([secrets_path], env: env)
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
# Backward compatibility aliases (deprecated)
|
43
|
+
alias_method :parse_default, :load
|
44
|
+
alias_method :read, :load
|
45
|
+
end
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
|
-
#
|
49
|
+
# Replace Rails::Secrets interface for backwards compatibility
|
49
50
|
# Works consistently across all Rails versions with warning suppression
|
50
51
|
if defined?(Rails)
|
51
52
|
module Rails
|
53
|
+
# Remove existing constant to avoid warnings
|
54
|
+
remove_const(:Secrets) if const_defined?(:Secrets, false)
|
52
55
|
Secrets = Secvault::RailsSecrets
|
53
56
|
end
|
54
57
|
end
|
data/lib/secvault/railtie.rb
CHANGED
@@ -2,34 +2,154 @@
|
|
2
2
|
|
3
3
|
require "rails/railtie"
|
4
4
|
|
5
|
+
# Extremely early hook to set up Rails.application.secrets before Application class is defined
|
6
|
+
if defined?(Rails)
|
7
|
+
# Set up a robust Rails.application with secrets support
|
8
|
+
unless Rails.respond_to?(:application) && Rails.application.respond_to?(:secrets)
|
9
|
+
# Create a minimal application-like object
|
10
|
+
temp_app = Object.new
|
11
|
+
|
12
|
+
# Add secrets method with default empty secrets that include needed encryption keys
|
13
|
+
temp_app.define_singleton_method(:secrets) do
|
14
|
+
@secrets ||= begin
|
15
|
+
secrets = ActiveSupport::OrderedOptions.new
|
16
|
+
|
17
|
+
# Add empty encryption section to prevent NoMethodError
|
18
|
+
secrets.encryption = {
|
19
|
+
primary_key: nil,
|
20
|
+
deterministic_key: nil,
|
21
|
+
key_derivation_salt: nil
|
22
|
+
}
|
23
|
+
|
24
|
+
secrets
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Set up Rails.application if it doesn't exist
|
29
|
+
Rails.define_singleton_method(:application) { temp_app } unless Rails.respond_to?(:application)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
5
33
|
module Secvault
|
6
34
|
class Railtie < Rails::Railtie
|
7
35
|
railtie_name :secvault
|
8
36
|
|
37
|
+
# Hook to set up early secrets access before application configuration
|
38
|
+
config.before_configuration do |app|
|
39
|
+
Secvault::EarlyLoader.setup_early_secrets(app)
|
40
|
+
end
|
41
|
+
|
9
42
|
initializer "secvault.initialize", before: :load_environment_hook do |app|
|
10
43
|
Secvault::Secrets.setup(app)
|
11
44
|
end
|
45
|
+
end
|
12
46
|
|
13
|
-
|
14
|
-
|
15
|
-
|
47
|
+
# Early loader class to handle secrets before application configuration
|
48
|
+
class EarlyLoader
|
49
|
+
class << self
|
50
|
+
def setup_early_secrets(app)
|
51
|
+
puts "[Secvault Debug] setup_early_secrets called" unless Rails.env.production?
|
52
|
+
|
53
|
+
if Rails.application.respond_to?(:secrets) && !Rails.application.secrets.empty?
|
54
|
+
puts "[Secvault Debug] Secrets already exist, skipping early load" unless Rails.env.production?
|
55
|
+
return
|
56
|
+
end
|
57
|
+
|
58
|
+
# Look for Secvault configuration in the app
|
59
|
+
secrets_config = find_secvault_config(app)
|
60
|
+
puts "[Secvault Debug] Found config: #{secrets_config&.keys}" unless Rails.env.production?
|
61
|
+
return unless secrets_config
|
16
62
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
63
|
+
begin
|
64
|
+
# Load secrets using the configuration found
|
65
|
+
all_secrets = Secvault::Secrets.parse(secrets_config[:files], env: Rails.env)
|
66
|
+
puts "[Secvault Debug] Loaded secrets keys: #{all_secrets.keys}" unless Rails.env.production?
|
21
67
|
|
22
|
-
|
68
|
+
# Set up Rails.application.secrets immediately
|
23
69
|
Rails.application.define_singleton_method(:secrets) do
|
24
70
|
@secrets ||= begin
|
25
71
|
current_secrets = ActiveSupport::OrderedOptions.new
|
26
|
-
|
27
|
-
current_secrets.
|
72
|
+
current_secrets.merge!(all_secrets)
|
73
|
+
puts "[Secvault Debug] Returning secrets with encryption: #{current_secrets.encryption}" unless Rails.env.production?
|
28
74
|
current_secrets
|
29
75
|
end
|
30
76
|
end
|
77
|
+
|
78
|
+
# Test the secrets immediately
|
79
|
+
test_encryption = Rails.application.secrets.encryption
|
80
|
+
puts "[Secvault Debug] Test access - encryption: #{test_encryption.class} - #{test_encryption}" unless Rails.env.production?
|
81
|
+
|
82
|
+
Rails.logger&.info "[Secvault] Early secrets loaded from #{secrets_config[:files].size} files" unless Rails.env.production?
|
83
|
+
rescue => e
|
84
|
+
Rails.logger&.warn "[Secvault] Failed to load early secrets: #{e.message}"
|
31
85
|
end
|
32
86
|
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def find_secvault_config(app)
|
91
|
+
# Look for Secvault configuration in various locations
|
92
|
+
config_locations = [
|
93
|
+
app.root.join("config/initializers/secvault.rb"),
|
94
|
+
app.root.join("config/secvault.rb")
|
95
|
+
]
|
96
|
+
|
97
|
+
config_locations.each do |config_file|
|
98
|
+
next unless config_file.exist?
|
99
|
+
|
100
|
+
config = parse_secvault_config(config_file)
|
101
|
+
return config if config
|
102
|
+
end
|
103
|
+
|
104
|
+
# Fallback to default configuration
|
105
|
+
default_files = [app.root.join("config/secrets.yml")]
|
106
|
+
|
107
|
+
# Check if neeto-commons-backend is available for default config
|
108
|
+
if defined?(NeetoCommonsBackend) && NeetoCommonsBackend.respond_to?(:shared_secrets_file)
|
109
|
+
default_files.unshift(NeetoCommonsBackend.shared_secrets_file)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Only return default if at least one file exists
|
113
|
+
existing_files = default_files.select(&:exist?)
|
114
|
+
return {files: existing_files} if existing_files.any?
|
115
|
+
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
|
119
|
+
def parse_secvault_config(config_file)
|
120
|
+
# Read the configuration file and extract Secvault.start! parameters
|
121
|
+
content = config_file.read
|
122
|
+
|
123
|
+
# Look for Secvault.start! calls
|
124
|
+
if /Secvault\.start!\s*\(/m.match?(content)
|
125
|
+
# Try to extract the files parameter using a simple regex
|
126
|
+
files_match = content.match(/files:\s*\[(.*?)\]/m)
|
127
|
+
if files_match
|
128
|
+
# Parse the files array (basic string parsing)
|
129
|
+
files_content = files_match[1]
|
130
|
+
files = []
|
131
|
+
|
132
|
+
# Handle various file specification patterns
|
133
|
+
files_content.scan(/["'](.*?)["']|([A-Za-z_][\w.]*\([^)]*\))/) do |quoted, method_call|
|
134
|
+
if quoted
|
135
|
+
files << Rails.root.join(quoted.strip)
|
136
|
+
elsif method_call
|
137
|
+
# Handle method calls like NeetoCommonsBackend.shared_secrets_file
|
138
|
+
if method_call.include?("NeetoCommonsBackend.shared_secrets_file") && defined?(NeetoCommonsBackend)
|
139
|
+
files << NeetoCommonsBackend.shared_secrets_file
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
return {files: files.compact} if files.any?
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
nil
|
149
|
+
rescue => e
|
150
|
+
Rails.logger&.warn "[Secvault] Failed to parse config file #{config_file}: #{e.message}"
|
151
|
+
nil
|
152
|
+
end
|
33
153
|
end
|
34
154
|
end
|
35
155
|
end
|
data/lib/secvault/secrets.rb
CHANGED
@@ -56,7 +56,7 @@ module Secvault
|
|
56
56
|
end
|
57
57
|
|
58
58
|
# Classic Rails::Secrets.parse implementation
|
59
|
-
# Parses plain YAML secrets files
|
59
|
+
# Parses plain YAML secrets files for specific environment
|
60
60
|
def parse(paths, env:)
|
61
61
|
paths.each_with_object({}) do |path, all_secrets|
|
62
62
|
# Handle string paths by converting to Pathname
|
@@ -66,22 +66,22 @@ module Secvault
|
|
66
66
|
# Read and process the plain YAML file content
|
67
67
|
source = path.read
|
68
68
|
|
69
|
-
# Process ERB and parse YAML
|
69
|
+
# Process ERB and parse YAML - using same method as Rails
|
70
70
|
erb_result = ERB.new(source).result
|
71
|
-
secrets = YAML.
|
71
|
+
secrets = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(erb_result) : YAML.load(erb_result)
|
72
72
|
|
73
73
|
secrets ||= {}
|
74
74
|
|
75
|
-
#
|
76
|
-
all_secrets.deep_merge!(secrets["shared"].deep_symbolize_keys) if secrets["shared"]
|
75
|
+
# Only load environment-specific section (YAML anchors handle sharing)
|
77
76
|
all_secrets.deep_merge!(secrets[env].deep_symbolize_keys) if secrets[env]
|
78
77
|
end
|
79
78
|
end
|
80
79
|
|
81
80
|
def read_secrets(secrets_path, env)
|
82
81
|
if secrets_path.exist?
|
83
|
-
# Handle plain YAML secrets.yml only
|
84
|
-
|
82
|
+
# Handle plain YAML secrets.yml only - using same method as Rails
|
83
|
+
erb_result = ERB.new(secrets_path.read).result
|
84
|
+
all_secrets = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(erb_result) : YAML.load(erb_result)
|
85
85
|
|
86
86
|
env_secrets = all_secrets[env.to_s]
|
87
87
|
return env_secrets.deep_symbolize_keys if env_secrets
|
data/lib/secvault/version.rb
CHANGED
data/lib/secvault.rb
CHANGED
@@ -65,6 +65,67 @@ module Secvault
|
|
65
65
|
defined?(Rails) && Rails::Secrets == Secvault::RailsSecrets
|
66
66
|
end
|
67
67
|
|
68
|
+
# Early setup method for use in config/application.rb before other configuration
|
69
|
+
# This ensures Rails.application has secrets available during application class definition
|
70
|
+
def setup_early_application_secrets!(files: nil, application_class: nil)
|
71
|
+
return false unless defined?(Rails)
|
72
|
+
|
73
|
+
# Default files if not provided
|
74
|
+
files ||= begin
|
75
|
+
default_files = ["config/secrets.yml"]
|
76
|
+
|
77
|
+
# Add neeto-commons-backend file if available
|
78
|
+
if defined?(NeetoCommonsBackend) && NeetoCommonsBackend.respond_to?(:shared_secrets_file)
|
79
|
+
default_files.unshift(NeetoCommonsBackend.shared_secrets_file)
|
80
|
+
end
|
81
|
+
|
82
|
+
default_files
|
83
|
+
end
|
84
|
+
|
85
|
+
# Create a temporary Rails.application if it doesn't exist
|
86
|
+
unless Rails.respond_to?(:application) && Rails.application
|
87
|
+
# Create a temporary application-like object with secrets
|
88
|
+
temp_app = Object.new
|
89
|
+
|
90
|
+
# Add lazy secrets loading
|
91
|
+
temp_app.define_singleton_method(:secrets) do
|
92
|
+
@secrets ||= begin
|
93
|
+
# Convert to full paths and filter existing files
|
94
|
+
file_paths = files.map do |file|
|
95
|
+
file.is_a?(Pathname) ? file : Rails.root.join(file)
|
96
|
+
end.select(&:exist?)
|
97
|
+
|
98
|
+
if file_paths.any?
|
99
|
+
# Load secrets using Secvault
|
100
|
+
all_secrets = Secvault::Secrets.parse(file_paths, env: Rails.env)
|
101
|
+
current_secrets = ActiveSupport::OrderedOptions.new
|
102
|
+
current_secrets.merge!(all_secrets)
|
103
|
+
current_secrets
|
104
|
+
else
|
105
|
+
# Return empty secrets if no files found but include encryption structure
|
106
|
+
secrets = ActiveSupport::OrderedOptions.new
|
107
|
+
secrets.encryption = ActiveSupport::OrderedOptions.new
|
108
|
+
secrets.encryption.primary_key = nil
|
109
|
+
secrets.encryption.deterministic_key = nil
|
110
|
+
secrets.encryption.key_derivation_salt = nil
|
111
|
+
secrets
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Set up Rails.application to point to this temporary object
|
117
|
+
Rails.define_singleton_method(:application) { temp_app }
|
118
|
+
end
|
119
|
+
|
120
|
+
true
|
121
|
+
rescue => e
|
122
|
+
warn "[Secvault] Early application secrets setup failed: #{e.message}"
|
123
|
+
false
|
124
|
+
end
|
125
|
+
|
126
|
+
# Alias for backward compatibility
|
127
|
+
alias_method :setup_early_secrets!, :setup_early_application_secrets!
|
128
|
+
|
68
129
|
def install!
|
69
130
|
return if defined?(Rails::Railtie).nil?
|
70
131
|
|
@@ -88,35 +149,34 @@ module Secvault
|
|
88
149
|
#
|
89
150
|
# Options:
|
90
151
|
# - files: Array of file paths (String or Pathname). Defaults to ['config/secrets.yml']
|
91
|
-
# - integrate_with_rails: Integrate with Rails.application.secrets (default:
|
152
|
+
# - integrate_with_rails: Integrate with Rails.application.secrets (default: false)
|
92
153
|
# - set_secret_key_base: Set Rails.application.config.secret_key_base from secrets (default: true)
|
93
154
|
# - hot_reload: Add reload_secrets! methods for development (default: true in development)
|
94
155
|
# - logger: Enable logging (default: true except production)
|
95
|
-
def start!(files: [], integrate_with_rails:
|
96
|
-
|
97
|
-
|
98
|
-
|
156
|
+
def start!(files: [], integrate_with_rails: false, set_secret_key_base: true,
|
157
|
+
hot_reload: ((defined?(Rails) && Rails.env.respond_to?(:development?)) ? Rails.env.development? : false),
|
158
|
+
logger: ((defined?(Rails) && Rails.env.respond_to?(:production?)) ? !Rails.env.production? : true))
|
99
159
|
# Default to config/secrets.yml if no files specified
|
100
160
|
files_to_load = files.empty? ? ["config/secrets.yml"] : Array(files)
|
101
|
-
|
161
|
+
|
102
162
|
# Convert to Pathname objects and resolve relative to Rails.root
|
103
163
|
file_paths = files_to_load.map do |file|
|
104
164
|
file.is_a?(Pathname) ? file : Rails.root.join(file)
|
105
165
|
end
|
106
|
-
|
166
|
+
|
107
167
|
# Load secrets into Secvault.secrets
|
108
168
|
load_secrets!(file_paths, logger: logger)
|
109
|
-
|
169
|
+
|
110
170
|
# Integrate with Rails if requested
|
111
171
|
if integrate_with_rails
|
112
172
|
setup_rails_integration!(file_paths, set_secret_key_base: set_secret_key_base, logger: logger)
|
113
173
|
end
|
114
|
-
|
174
|
+
|
115
175
|
# Add hot reload functionality if requested
|
116
176
|
if hot_reload
|
117
177
|
add_hot_reload!(file_paths)
|
118
178
|
end
|
119
|
-
|
179
|
+
|
120
180
|
true
|
121
181
|
rescue => e
|
122
182
|
Rails.logger&.error "[Secvault] Failed to start: #{e.message}" if defined?(Rails) && logger
|
@@ -126,24 +186,24 @@ module Secvault
|
|
126
186
|
private
|
127
187
|
|
128
188
|
# 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))
|
189
|
+
def load_secrets!(file_paths, logger: ((defined?(Rails) && Rails.env.respond_to?(:production?)) ? !Rails.env.production? : true))
|
130
190
|
existing_files = file_paths.select(&:exist?)
|
131
|
-
|
191
|
+
|
132
192
|
if existing_files.any?
|
133
193
|
# Load and merge all secrets files
|
134
194
|
merged_secrets = Secvault::Secrets.parse(existing_files, env: Rails.env)
|
135
|
-
|
195
|
+
|
136
196
|
# Store in internal storage with ActiveSupport::OrderedOptions for compatibility
|
137
197
|
@@loaded_secrets = ActiveSupport::OrderedOptions.new
|
138
198
|
@@loaded_secrets.merge!(merged_secrets)
|
139
|
-
|
199
|
+
|
140
200
|
# Log successful loading
|
141
201
|
if logger
|
142
202
|
file_names = existing_files.map(&:basename)
|
143
203
|
Rails.logger&.info "[Secvault] Loaded #{existing_files.size} files: #{file_names.join(", ")}"
|
144
204
|
Rails.logger&.info "[Secvault] Parsed #{merged_secrets.keys.size} secret keys for #{Rails.env}"
|
145
205
|
end
|
146
|
-
|
206
|
+
|
147
207
|
true
|
148
208
|
else
|
149
209
|
Rails.logger&.warn "[Secvault] No secrets files found" if logger
|
@@ -151,13 +211,13 @@ module Secvault
|
|
151
211
|
false
|
152
212
|
end
|
153
213
|
end
|
154
|
-
|
214
|
+
|
155
215
|
# 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))
|
216
|
+
def setup_rails_integration!(file_paths, set_secret_key_base: true, logger: ((defined?(Rails) && Rails.env.respond_to?(:production?)) ? !Rails.env.production? : true))
|
157
217
|
# Override native Rails::Secrets with Secvault implementation
|
158
218
|
Rails.send(:remove_const, :Secrets) if defined?(Rails::Secrets)
|
159
219
|
Rails.const_set(:Secrets, Secvault::RailsSecrets)
|
160
|
-
|
220
|
+
|
161
221
|
# Set up Rails.application.secrets replacement in after_initialize
|
162
222
|
Rails.application.config.after_initialize do
|
163
223
|
if @@loaded_secrets && !@@loaded_secrets.empty?
|
@@ -165,51 +225,99 @@ module Secvault
|
|
165
225
|
Rails.application.define_singleton_method(:secrets) do
|
166
226
|
@@loaded_secrets
|
167
227
|
end
|
168
|
-
|
228
|
+
|
169
229
|
# Set secret_key_base in Rails config to avoid accessing it from secrets
|
170
230
|
if set_secret_key_base && @@loaded_secrets.key?(:secret_key_base)
|
171
231
|
Rails.application.config.secret_key_base = @@loaded_secrets[:secret_key_base]
|
172
232
|
Rails.logger&.info "[Secvault] Set Rails.application.config.secret_key_base from secrets" if logger
|
173
233
|
end
|
174
|
-
|
234
|
+
|
175
235
|
# Log integration success (except in production)
|
176
236
|
if logger
|
177
237
|
Rails.logger&.info "[Secvault] Rails integration complete. #{@@loaded_secrets.keys.size} secret keys available."
|
178
238
|
end
|
179
|
-
|
180
|
-
Rails.logger&.warn "[Secvault] No secrets loaded for Rails integration"
|
239
|
+
elsif logger
|
240
|
+
Rails.logger&.warn "[Secvault] No secrets loaded for Rails integration"
|
181
241
|
end
|
182
242
|
end
|
183
243
|
end
|
184
|
-
|
244
|
+
|
185
245
|
# Add hot reload functionality for development
|
186
246
|
def add_hot_reload!(file_paths)
|
187
247
|
# Define reload method on Rails.application
|
188
248
|
Rails.application.define_singleton_method(:reload_secrets!) do
|
189
249
|
# Reload secrets
|
190
250
|
Secvault.send(:load_secrets!, file_paths, logger: true)
|
191
|
-
|
251
|
+
|
192
252
|
# Re-apply Rails integration if needed
|
193
253
|
if Secvault.rails_integrated? && @@loaded_secrets
|
194
254
|
Rails.application.define_singleton_method(:secrets) do
|
195
255
|
@@loaded_secrets
|
196
256
|
end
|
197
257
|
end
|
198
|
-
|
258
|
+
|
199
259
|
puts "🔄 Hot reloaded secrets from #{file_paths.size} files"
|
200
260
|
true
|
201
261
|
end
|
202
|
-
|
262
|
+
|
203
263
|
# Also make it available as a top-level method
|
204
264
|
Object.define_method(:reload_secrets!) do
|
205
265
|
Rails.application.reload_secrets!
|
206
266
|
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?)
|
209
|
-
end
|
210
|
-
|
211
|
-
public
|
212
267
|
|
268
|
+
Rails.logger&.info "[Secvault] Hot reload enabled. Use reload_secrets! to refresh secrets." unless defined?(Rails) && Rails.env.respond_to?(:production?) && Rails.env.production?
|
269
|
+
end
|
213
270
|
end
|
214
271
|
|
215
|
-
|
272
|
+
# Auto-install and setup when Rails is available
|
273
|
+
if defined?(Rails)
|
274
|
+
Secvault.install!
|
275
|
+
|
276
|
+
# Immediate setup for early access during application loading
|
277
|
+
begin
|
278
|
+
# Try to detect and load secrets immediately if Rails.root is available
|
279
|
+
if Rails.respond_to?(:root) && Rails.root
|
280
|
+
# Look for default secrets or configuration
|
281
|
+
default_secrets_file = Rails.root.join("config/secrets.yml")
|
282
|
+
commons_secrets_file = nil
|
283
|
+
|
284
|
+
# Check for neeto-commons-backend integration
|
285
|
+
if defined?(NeetoCommonsBackend) && NeetoCommonsBackend.respond_to?(:shared_secrets_file)
|
286
|
+
commons_secrets_file = NeetoCommonsBackend.shared_secrets_file
|
287
|
+
end
|
288
|
+
|
289
|
+
files_to_load = [commons_secrets_file, default_secrets_file].compact.select(&:exist?)
|
290
|
+
|
291
|
+
if files_to_load.any? && Rails.respond_to?(:env)
|
292
|
+
# Load secrets immediately
|
293
|
+
all_secrets = Secvault::Secrets.parse(files_to_load, env: Rails.env)
|
294
|
+
|
295
|
+
# Set up Rails.application.secrets if Rails.application exists
|
296
|
+
if Rails.respond_to?(:application) && Rails.application
|
297
|
+
Rails.application.define_singleton_method(:secrets) do
|
298
|
+
@secrets ||= begin
|
299
|
+
current_secrets = ActiveSupport::OrderedOptions.new
|
300
|
+
current_secrets.merge!(all_secrets)
|
301
|
+
current_secrets
|
302
|
+
end
|
303
|
+
end
|
304
|
+
else
|
305
|
+
# Create a minimal Rails.application for early access
|
306
|
+
temp_app = Object.new
|
307
|
+
temp_app.define_singleton_method(:secrets) do
|
308
|
+
@secrets ||= begin
|
309
|
+
current_secrets = ActiveSupport::OrderedOptions.new
|
310
|
+
current_secrets.merge!(all_secrets)
|
311
|
+
current_secrets
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
Rails.define_singleton_method(:application) { temp_app } unless Rails.respond_to?(:application)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
rescue => e
|
320
|
+
# Silent fail - normal initialization will handle it
|
321
|
+
warn "[Secvault] Early auto-load failed: #{e.message}" unless Rails.env&.production?
|
322
|
+
end
|
323
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secvault
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Unnikrishnan KP
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-09-
|
11
|
+
date: 2025-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '2.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec_junit_formatter
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.6'
|
41
55
|
description: Secvault restores the classic Rails secrets.yml functionality that was
|
42
56
|
removed in Rails 7.2, using simple, plain YAML files for environment-specific secrets
|
43
57
|
management. Compatible with Rails 7.1+, 7.2+ and 8.0+.
|
@@ -52,6 +66,7 @@ files:
|
|
52
66
|
- LICENSE.txt
|
53
67
|
- README.md
|
54
68
|
- Rakefile
|
69
|
+
- SECURITY.md
|
55
70
|
- lib/secvault.rb
|
56
71
|
- lib/secvault/rails_secrets.rb
|
57
72
|
- lib/secvault/railtie.rb
|
@@ -73,7 +88,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
88
|
requirements:
|
74
89
|
- - ">="
|
75
90
|
- !ruby/object:Gem::Version
|
76
|
-
version: 3.
|
91
|
+
version: 3.2.0
|
77
92
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
93
|
requirements:
|
79
94
|
- - ">="
|