secvault 2.3.0 → 2.5.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 +194 -24
- data/lib/secvault/version.rb +1 -1
- data/lib/secvault.rb +85 -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: 7aa65a706270754c5654d4ddac89114d5c6f59f80c2bd1480d11dc0b350a57d9
|
|
4
|
+
data.tar.gz: 9ce9a4d9661505ba37a8c827d8f5811c8b9ff0b2fab8f8a8b89bb9f53fb62618
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ebd99a7e5c479c1bf98f7180414bcf7b77b45f70a19d56b3b20d734da069ed5f4d18b6855356a89d47475c6955f8ce5a4edc161e2ee13c5d4ab1494a4be38855
|
|
7
|
+
data.tar.gz: 888b561a34ff6be3798accb339d812d0456e3e10df0f4725c2c522e080ccef70c6dc23e54cae76878b5e4a67a44b4b63527b5bd0386133907b3df9a6bbec3df5
|
data/README.md
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
# Secvault
|
|
2
2
|
|
|
3
|
-
Restores the classic Rails `secrets.yml` functionality that was removed in Rails 7.2. Uses simple, plain YAML files for environment-specific secrets management.
|
|
3
|
+
Restores the classic Rails `secrets.yml` functionality that was removed in Rails 7.2. Uses simple, plain YAML files for environment-specific secrets management with powerful features like YAML defaults, ERB interpolation, and multi-file configurations.
|
|
4
4
|
|
|
5
5
|
**Rails Version Support:**
|
|
6
6
|
- **Rails 7.1 and older**: Manual setup (see Older Rails Integration below)
|
|
7
7
|
- **Rails 7.2+**: Automatic setup
|
|
8
8
|
- **Rails 8.0+**: Full compatibility
|
|
9
9
|
|
|
10
|
+
## ✨ Key Features
|
|
11
|
+
|
|
12
|
+
- 🔗 **YAML Anchor/Alias Support**: Use `default: &default` for shared configuration
|
|
13
|
+
- 🌍 **ERB Interpolation**: Environment variables with type conversion (`ENV['VAR'].to_i`, boolean logic)
|
|
14
|
+
- 📁 **Multi-File Loading**: Merge multiple YAML files (e.g., base + OAuth + local overrides)
|
|
15
|
+
- 🔄 **Environment Switching**: Load different environments dynamically
|
|
16
|
+
- 🛠️ **Development Tools**: Hot-reload secrets without server restart
|
|
17
|
+
- 🔍 **Utility Methods**: `Secvault.active?` to check integration status
|
|
18
|
+
- 🏗️ **Flexible Organization**: Feature-based, environment-based, or namespace-based file structures
|
|
19
|
+
|
|
10
20
|
## Installation
|
|
11
21
|
|
|
12
22
|
```ruby
|
|
@@ -34,32 +44,99 @@ Rails.application.secrets.api_key
|
|
|
34
44
|
Rails.application.secrets.database_password
|
|
35
45
|
```
|
|
36
46
|
|
|
37
|
-
**Example secrets.yml:**
|
|
47
|
+
**Example secrets.yml with YAML defaults and ERB:**
|
|
38
48
|
```yaml
|
|
49
|
+
# YAML defaults - inherited by all environments
|
|
50
|
+
default: &default
|
|
51
|
+
app_name: "My Application"
|
|
52
|
+
database:
|
|
53
|
+
adapter: "postgresql"
|
|
54
|
+
pool: 5
|
|
55
|
+
timeout: 5000
|
|
56
|
+
api:
|
|
57
|
+
timeout: 30
|
|
58
|
+
retries: 3
|
|
59
|
+
|
|
39
60
|
development:
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
<<: *default # Inherit defaults
|
|
62
|
+
api_key: "dev_api_key_123"
|
|
63
|
+
database:
|
|
64
|
+
host: "localhost"
|
|
65
|
+
name: "myapp_development"
|
|
66
|
+
api:
|
|
67
|
+
base_url: "http://localhost:3000" # Override default
|
|
42
68
|
|
|
43
69
|
production:
|
|
70
|
+
<<: *default # Inherit defaults
|
|
44
71
|
api_key: <%= ENV['API_KEY'] %>
|
|
45
|
-
|
|
72
|
+
database:
|
|
73
|
+
host: <%= ENV['DATABASE_HOST'] %>
|
|
74
|
+
name: <%= ENV['DATABASE_NAME'] %>
|
|
75
|
+
pool: <%= ENV.fetch('DATABASE_POOL', '10').to_i %> # Type conversion
|
|
76
|
+
api:
|
|
77
|
+
base_url: <%= ENV['API_BASE_URL'] %>
|
|
78
|
+
|
|
79
|
+
# Boolean conversion
|
|
80
|
+
features:
|
|
81
|
+
new_ui: <%= ENV.fetch('FEATURE_NEW_UI', 'true') == 'true' %>
|
|
82
|
+
|
|
83
|
+
# Array conversion
|
|
84
|
+
oauth_scopes: <%= ENV.fetch('OAUTH_SCOPES', 'email,profile').split(',') %>
|
|
46
85
|
```
|
|
47
86
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
87
|
+
## Multi-File Configuration
|
|
88
|
+
|
|
89
|
+
Organize secrets across multiple files with a **super clean API**:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
# config/initializers/secvault.rb
|
|
93
|
+
require "secvault"
|
|
94
|
+
|
|
95
|
+
# That's it! Just pass your files array
|
|
96
|
+
Secvault.setup_multi_file!([
|
|
97
|
+
'config/secrets.yml', # Base secrets
|
|
98
|
+
'config/secrets.oauth.yml', # OAuth & APIs
|
|
99
|
+
'config/secrets.local.yml' # Local overrides
|
|
100
|
+
])
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**What this does automatically:**
|
|
104
|
+
- ✅ Sets up Rails 7.1 compatibility (calls `setup_backward_compatibility_with_older_rails!`)
|
|
105
|
+
- ✅ Loads and merges all files in order (later files override earlier ones)
|
|
106
|
+
- ✅ Handles missing files gracefully
|
|
107
|
+
- ✅ Adds `reload_secrets!` method in development
|
|
108
|
+
- ✅ Provides logging (except in production)
|
|
109
|
+
- ✅ Creates Rails.application.secrets with merged configuration
|
|
110
|
+
|
|
111
|
+
**File organization example:**
|
|
112
|
+
```
|
|
113
|
+
config/
|
|
114
|
+
├── secrets.yml # Base application secrets
|
|
115
|
+
├── secrets.oauth.yml # OAuth providers & external APIs
|
|
116
|
+
├── secrets.local.yml # Local development overrides (gitignored)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Advanced options:**
|
|
120
|
+
```ruby
|
|
121
|
+
# Disable reload helper or logging
|
|
122
|
+
Secvault.setup_multi_file!(files, reload_method: false, logger: false)
|
|
123
|
+
|
|
124
|
+
# Use Pathname objects if needed
|
|
125
|
+
Secvault.setup_multi_file!([
|
|
126
|
+
Rails.root.join('config', 'secrets.yml'),
|
|
127
|
+
Rails.root.join('config', 'secrets.oauth.yml')
|
|
128
|
+
])
|
|
52
129
|
```
|
|
53
130
|
|
|
54
131
|
## Advanced Usage
|
|
55
132
|
|
|
56
|
-
**
|
|
133
|
+
**Manual multi-file parsing:**
|
|
57
134
|
```ruby
|
|
58
135
|
# Parse multiple files - later files override earlier ones
|
|
59
136
|
secrets = Rails::Secrets.parse([
|
|
60
137
|
'config/secrets.yml',
|
|
61
|
-
'config/secrets.
|
|
62
|
-
'config/secrets.
|
|
138
|
+
'config/secrets.oauth.yml',
|
|
139
|
+
'config/secrets.local.yml'
|
|
63
140
|
], env: Rails.env)
|
|
64
141
|
```
|
|
65
142
|
|
|
@@ -72,16 +149,53 @@ production_secrets = Rails::Secrets.load(env: 'production')
|
|
|
72
149
|
dev_secrets = Rails::Secrets.load(env: 'development')
|
|
73
150
|
```
|
|
74
151
|
|
|
75
|
-
**
|
|
152
|
+
**Environment-specific loading:**
|
|
76
153
|
```ruby
|
|
77
|
-
#
|
|
78
|
-
|
|
154
|
+
# Load production secrets in any environment
|
|
155
|
+
production_secrets = Rails::Secrets.load(env: 'production')
|
|
79
156
|
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
157
|
+
# Load development secrets
|
|
158
|
+
dev_secrets = Rails::Secrets.load(env: 'development')
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## ERB Features & Type Conversion
|
|
162
|
+
|
|
163
|
+
Secvault supports powerful ERB templating with automatic type conversion:
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
production:
|
|
167
|
+
# String interpolation
|
|
168
|
+
api_key: <%= ENV['API_KEY'] %>
|
|
169
|
+
|
|
170
|
+
# Integer conversion
|
|
171
|
+
database_pool: <%= ENV.fetch('DB_POOL', '10').to_i %>
|
|
172
|
+
|
|
173
|
+
# Boolean conversion
|
|
174
|
+
debug_enabled: <%= ENV.fetch('DEBUG', 'false') == 'true' %>
|
|
175
|
+
|
|
176
|
+
# Array conversion
|
|
177
|
+
allowed_hosts: <%= ENV.fetch('HOSTS', 'localhost,127.0.0.1').split(',') %>
|
|
178
|
+
|
|
179
|
+
# Fallback values
|
|
180
|
+
timeout: <%= ENV.fetch('TIMEOUT', '30').to_i %>
|
|
181
|
+
adapter: <%= ENV.fetch('DB_ADAPTER', 'postgresql') %>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Development Tools
|
|
185
|
+
|
|
186
|
+
**Hot-reload secrets (automatically available in development):**
|
|
187
|
+
```ruby
|
|
188
|
+
# In Rails console - automatically added by setup_multi_file!
|
|
189
|
+
reload_secrets! # Reloads all configured files without server restart
|
|
190
|
+
# 🔄 Reloaded secrets from 3 files
|
|
191
|
+
|
|
192
|
+
# Also available as:
|
|
193
|
+
Rails.application.reload_secrets!
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Check integration status:**
|
|
197
|
+
```ruby
|
|
198
|
+
Secvault.active? # Returns true if Secvault is managing secrets
|
|
85
199
|
```
|
|
86
200
|
|
|
87
201
|
## Older Rails Integration
|
|
@@ -102,12 +216,68 @@ Rails::Secrets.load # ✅ Load default config/secrets.yml
|
|
|
102
216
|
Rails::Secrets.parse(['custom.yml'], env: Rails.env) # ✅ Parse custom files
|
|
103
217
|
```
|
|
104
218
|
|
|
219
|
+
**Check if Secvault is active:**
|
|
220
|
+
```ruby
|
|
221
|
+
if Secvault.active?
|
|
222
|
+
puts "Using Secvault for secrets management"
|
|
223
|
+
else
|
|
224
|
+
puts "Using default Rails secrets functionality"
|
|
225
|
+
end
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Usage Examples
|
|
229
|
+
|
|
230
|
+
**Basic usage:**
|
|
231
|
+
```ruby
|
|
232
|
+
# Access secrets
|
|
233
|
+
Rails.application.secrets.api_key
|
|
234
|
+
Rails.application.secrets.database.host
|
|
235
|
+
Rails.application.secrets.oauth.google.client_id
|
|
236
|
+
|
|
237
|
+
# With YAML defaults, you get deep merging:
|
|
238
|
+
Rails.application.secrets.database.adapter # "postgresql" (from default)
|
|
239
|
+
Rails.application.secrets.database.host # "localhost" (from environment)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Multi-file merging:**
|
|
243
|
+
```ruby
|
|
244
|
+
# Files loaded in order: base → oauth → local
|
|
245
|
+
# Later files override earlier ones for the same keys
|
|
246
|
+
# Hash values are deep merged, scalars are replaced
|
|
247
|
+
|
|
248
|
+
Rails.application.secrets.api_key # Could be from base or local file
|
|
249
|
+
Rails.application.secrets.oauth.google # From oauth file
|
|
250
|
+
Rails.application.secrets.features.debug # From local file override
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Security Best Practices
|
|
105
254
|
|
|
106
|
-
|
|
255
|
+
### ⚠️ Production Security
|
|
256
|
+
- **Never commit production secrets** to version control
|
|
257
|
+
- **Use environment variables** in production with ERB: `<%= ENV['SECRET'] %>`
|
|
258
|
+
- **Use ENV.fetch()** with fallbacks: `<%= ENV.fetch('SECRET', 'default') %>`
|
|
107
259
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
260
|
+
### 📝 File Management
|
|
261
|
+
- **Add sensitive files** to `.gitignore`:
|
|
262
|
+
```gitignore
|
|
263
|
+
config/secrets.yml # If contains sensitive data
|
|
264
|
+
config/secrets.local.yml # Local development overrides
|
|
265
|
+
config/secrets.production.yml # If used
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 🔑 Recommended Structure
|
|
269
|
+
```yaml
|
|
270
|
+
# ✅ GOOD: Base file with safe defaults
|
|
271
|
+
development:
|
|
272
|
+
api_key: "safe_dev_key_for_team"
|
|
273
|
+
|
|
274
|
+
production:
|
|
275
|
+
api_key: <%= ENV['API_KEY'] %> # ✅ From environment
|
|
276
|
+
|
|
277
|
+
# ❌ BAD: Secrets hardcoded in base file
|
|
278
|
+
production:
|
|
279
|
+
api_key: "super_secret_production_key" # ❌ Never do this
|
|
280
|
+
```
|
|
111
281
|
|
|
112
282
|
## License
|
|
113
283
|
|
data/lib/secvault/version.rb
CHANGED
data/lib/secvault.rb
CHANGED
|
@@ -58,6 +58,11 @@ module Secvault
|
|
|
58
58
|
|
|
59
59
|
extend self
|
|
60
60
|
|
|
61
|
+
# Check if Secvault is currently active in the Rails application
|
|
62
|
+
def active?
|
|
63
|
+
defined?(Rails) && Rails::Secrets == Secvault::RailsSecrets
|
|
64
|
+
end
|
|
65
|
+
|
|
61
66
|
def install!
|
|
62
67
|
return if defined?(Rails::Railtie).nil?
|
|
63
68
|
|
|
@@ -110,8 +115,87 @@ module Secvault
|
|
|
110
115
|
end
|
|
111
116
|
end
|
|
112
117
|
|
|
113
|
-
#
|
|
118
|
+
# Set up multi-file secrets loading with a clean API
|
|
119
|
+
# Just pass an array of file paths and Secvault handles the rest
|
|
120
|
+
#
|
|
121
|
+
# Usage in an initializer:
|
|
122
|
+
# Secvault.setup_multi_file!([
|
|
123
|
+
# 'config/secrets.yml',
|
|
124
|
+
# 'config/secrets.oauth.yml',
|
|
125
|
+
# 'config/secrets.local.yml'
|
|
126
|
+
# ])
|
|
127
|
+
#
|
|
128
|
+
# Options:
|
|
129
|
+
# - files: Array of file paths (String or Pathname)
|
|
130
|
+
# - reload_method: Add a reload helper method (default: true in development)
|
|
131
|
+
# - logger: Enable/disable logging (default: true except in production)
|
|
132
|
+
def setup_multi_file!(files, reload_method: Rails.env.development?, logger: !Rails.env.production?)
|
|
133
|
+
# Ensure Secvault integration is active
|
|
134
|
+
setup_backward_compatibility_with_older_rails! unless active?
|
|
135
|
+
|
|
136
|
+
# Convert strings to Pathname objects and resolve relative to Rails.root
|
|
137
|
+
file_paths = Array(files).map do |file|
|
|
138
|
+
file.is_a?(Pathname) ? file : Rails.root.join(file)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Set up the multi-file loading
|
|
142
|
+
Rails.application.config.after_initialize do
|
|
143
|
+
load_multi_file_secrets!(file_paths, logger: logger)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Add reload helper in development
|
|
147
|
+
if reload_method
|
|
148
|
+
add_reload_helper!(file_paths)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Load secrets from multiple files and merge them
|
|
153
|
+
def load_multi_file_secrets!(file_paths, logger: !Rails.env.production?)
|
|
154
|
+
existing_files = file_paths.select(&:exist?)
|
|
155
|
+
|
|
156
|
+
if existing_files.any?
|
|
157
|
+
# Load and merge all secrets files
|
|
158
|
+
merged_secrets = Rails::Secrets.parse(existing_files, env: Rails.env)
|
|
159
|
+
|
|
160
|
+
# Create ActiveSupport::OrderedOptions object for Rails compatibility
|
|
161
|
+
secrets_object = ActiveSupport::OrderedOptions.new
|
|
162
|
+
secrets_object.merge!(merged_secrets)
|
|
163
|
+
|
|
164
|
+
# Replace Rails.application.secrets
|
|
165
|
+
Rails.application.define_singleton_method(:secrets) { secrets_object }
|
|
166
|
+
|
|
167
|
+
# Log successful loading
|
|
168
|
+
if logger
|
|
169
|
+
file_names = existing_files.map(&:basename)
|
|
170
|
+
Rails.logger&.info "[Secvault Multi-File] Loaded #{existing_files.size} files: #{file_names.join(', ')}"
|
|
171
|
+
Rails.logger&.info "[Secvault Multi-File] Merged #{merged_secrets.keys.size} secret keys for #{Rails.env}"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
merged_secrets
|
|
175
|
+
else
|
|
176
|
+
Rails.logger&.warn "[Secvault Multi-File] No secrets files found" if logger
|
|
177
|
+
{}
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Add reload helper method for development
|
|
182
|
+
def add_reload_helper!(file_paths)
|
|
183
|
+
# Define reload method on Rails.application
|
|
184
|
+
Rails.application.define_singleton_method(:reload_secrets!) do
|
|
185
|
+
Secvault.load_multi_file_secrets!(file_paths, logger: true)
|
|
186
|
+
puts "🔄 Reloaded secrets from #{file_paths.size} files"
|
|
187
|
+
true
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Also make it available as a top-level method
|
|
191
|
+
Object.define_method(:reload_secrets!) do
|
|
192
|
+
Rails.application.reload_secrets!
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Backward compatibility aliases
|
|
114
197
|
alias_method :setup_rails_71_integration!, :setup_backward_compatibility_with_older_rails!
|
|
198
|
+
alias_method :setup_multi_files!, :setup_multi_file! # Alternative name
|
|
115
199
|
end
|
|
116
200
|
|
|
117
201
|
Secvault.install! if defined?(Rails)
|