secvault 2.5.0 → 2.7.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/.rspec +1 -1
- data/README.md +42 -225
- data/lib/secvault/rails_secrets.rb +8 -8
- data/lib/secvault/railtie.rb +4 -5
- data/lib/secvault/secrets.rb +19 -22
- data/lib/secvault/version.rb +1 -1
- data/lib/secvault.rb +124 -26
- metadata +2 -8
- data/CHANGELOG.md +0 -185
- data/CODE_OF_CONDUCT.md +0 -132
- data/lib/secvault/generators/secrets_generator.rb +0 -100
- data/lib/secvault/secrets_helper.rb +0 -44
- data/secvault-2.0.0.gem +0 -0
- data/sig/secvault.rbs +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 51dbcfd2c3b3073fb06223b681fb358cf8c74aae00c90663470edc19b3a40cd0
|
|
4
|
+
data.tar.gz: f78b9e5a1bdcb9148d58e31791738179b38a401ae0416920b4a6ee557e024ffd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 99d55935f3754ed807d306e94c530259ae34c82d43e1b2827ace62a2e33fd8bed291d8af358597aa68b71d78737af402273644e2b5d9475c5540be234b2d5985
|
|
7
|
+
data.tar.gz: d24dfa1e4613602a15ee7d40aeabedb089edf9e88ea136960ca039d6298252d8e7cd4e8a50d6f815cedcbde9d6192320814ccf6694797633225a29e98b993842
|
data/.rspec
CHANGED
data/README.md
CHANGED
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
# Secvault
|
|
2
2
|
|
|
3
|
-
Restores
|
|
3
|
+
Restores Rails `secrets.yml` functionality for environment-specific secrets management using YAML files with ERB templating.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- **Rails 7.1 and older**: Manual setup (see Older Rails Integration below)
|
|
7
|
-
- **Rails 7.2+**: Automatic setup
|
|
8
|
-
- **Rails 8.0+**: Full compatibility
|
|
9
|
-
|
|
10
|
-
## ✨ Key Features
|
|
5
|
+
## Rails Version Support
|
|
11
6
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
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
|
|
7
|
+
- **Rails 7.2+**: Automatic setup (drop-in replacement for removed functionality)
|
|
8
|
+
- **Rails 7.1**: Manual setup required
|
|
9
|
+
- **Rails 8.0+**: Full compatibility
|
|
19
10
|
|
|
20
11
|
## Installation
|
|
21
12
|
|
|
@@ -24,261 +15,87 @@ Restores the classic Rails `secrets.yml` functionality that was removed in Rails
|
|
|
24
15
|
gem 'secvault'
|
|
25
16
|
```
|
|
26
17
|
|
|
27
|
-
|
|
28
|
-
bundle install
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Quick Start (Rails 7.2+)
|
|
18
|
+
## Quick Start
|
|
32
19
|
|
|
33
|
-
|
|
34
|
-
# 1. Create secrets.yml
|
|
35
|
-
touch config/secrets.yml
|
|
20
|
+
Create `config/secrets.yml`:
|
|
36
21
|
|
|
37
|
-
# 2. Edit with your favorite editor
|
|
38
|
-
$EDITOR config/secrets.yml
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
**Usage in your app:**
|
|
42
|
-
```ruby
|
|
43
|
-
Rails.application.secrets.api_key
|
|
44
|
-
Rails.application.secrets.database_password
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**Example secrets.yml with YAML defaults and ERB:**
|
|
48
22
|
```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
|
-
|
|
60
23
|
development:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
database:
|
|
64
|
-
host: "localhost"
|
|
65
|
-
name: "myapp_development"
|
|
66
|
-
api:
|
|
67
|
-
base_url: "http://localhost:3000" # Override default
|
|
24
|
+
api_key: "dev_key_123"
|
|
25
|
+
database_url: "postgresql://localhost/myapp_dev"
|
|
68
26
|
|
|
69
27
|
production:
|
|
70
|
-
<<: *default # Inherit defaults
|
|
71
28
|
api_key: <%= ENV['API_KEY'] %>
|
|
72
|
-
|
|
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(',') %>
|
|
29
|
+
database_url: <%= ENV['DATABASE_URL'] %>
|
|
85
30
|
```
|
|
86
31
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Organize secrets across multiple files with a **super clean API**:
|
|
32
|
+
Access secrets in your app:
|
|
90
33
|
|
|
91
34
|
```ruby
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
])
|
|
35
|
+
Rails.application.secrets.api_key
|
|
36
|
+
Rails.application.secrets.database_url
|
|
101
37
|
```
|
|
102
38
|
|
|
103
|
-
|
|
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
|
|
39
|
+
## Multi-File Configuration
|
|
110
40
|
|
|
111
|
-
|
|
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
|
-
```
|
|
41
|
+
Load and merge multiple secrets files:
|
|
118
42
|
|
|
119
|
-
**Advanced options:**
|
|
120
43
|
```ruby
|
|
121
|
-
#
|
|
122
|
-
Secvault.setup_multi_file!(files, reload_method: false, logger: false)
|
|
123
|
-
|
|
124
|
-
# Use Pathname objects if needed
|
|
44
|
+
# config/initializers/secvault.rb
|
|
125
45
|
Secvault.setup_multi_file!([
|
|
126
|
-
Rails.root.join('config', 'secrets.yml'),
|
|
127
|
-
Rails.root.join('config', 'secrets.oauth.yml')
|
|
128
|
-
])
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Advanced Usage
|
|
132
|
-
|
|
133
|
-
**Manual multi-file parsing:**
|
|
134
|
-
```ruby
|
|
135
|
-
# Parse multiple files - later files override earlier ones
|
|
136
|
-
secrets = Rails::Secrets.parse([
|
|
137
46
|
'config/secrets.yml',
|
|
138
47
|
'config/secrets.oauth.yml',
|
|
139
48
|
'config/secrets.local.yml'
|
|
140
|
-
]
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
**Load specific environment:**
|
|
144
|
-
```ruby
|
|
145
|
-
# Load production secrets in any environment
|
|
146
|
-
production_secrets = Rails::Secrets.load(env: 'production')
|
|
147
|
-
|
|
148
|
-
# Load development secrets
|
|
149
|
-
dev_secrets = Rails::Secrets.load(env: 'development')
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**Environment-specific loading:**
|
|
153
|
-
```ruby
|
|
154
|
-
# Load production secrets in any environment
|
|
155
|
-
production_secrets = Rails::Secrets.load(env: 'production')
|
|
156
|
-
|
|
157
|
-
# Load development secrets
|
|
158
|
-
dev_secrets = Rails::Secrets.load(env: 'development')
|
|
49
|
+
])
|
|
159
50
|
```
|
|
160
51
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
Secvault supports powerful ERB templating with automatic type conversion:
|
|
52
|
+
Files are merged in order with deep merge support for nested hashes.
|
|
164
53
|
|
|
165
|
-
|
|
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
|
|
54
|
+
## Manual API
|
|
185
55
|
|
|
186
|
-
**Hot-reload secrets (automatically available in development):**
|
|
187
56
|
```ruby
|
|
188
|
-
#
|
|
189
|
-
|
|
190
|
-
# 🔄 Reloaded secrets from 3 files
|
|
57
|
+
# Parse specific files
|
|
58
|
+
secrets = Rails::Secrets.parse(['config/secrets.yml'], env: Rails.env)
|
|
191
59
|
|
|
192
|
-
#
|
|
193
|
-
Rails.
|
|
194
|
-
```
|
|
60
|
+
# Load default config/secrets.yml
|
|
61
|
+
secrets = Rails::Secrets.load(env: 'production')
|
|
195
62
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
Secvault.active? # Returns true if Secvault is managing secrets
|
|
63
|
+
# Check if active
|
|
64
|
+
Secvault.active? # => true/false
|
|
199
65
|
```
|
|
200
66
|
|
|
201
|
-
##
|
|
67
|
+
## Rails 7.1 Integration
|
|
202
68
|
|
|
203
|
-
For Rails
|
|
69
|
+
For Rails 7.1 with existing secrets functionality:
|
|
204
70
|
|
|
205
71
|
```ruby
|
|
206
72
|
# config/initializers/secvault.rb
|
|
207
73
|
Secvault.setup_backward_compatibility_with_older_rails!
|
|
208
74
|
```
|
|
209
75
|
|
|
210
|
-
|
|
76
|
+
## ERB Templating
|
|
211
77
|
|
|
212
|
-
|
|
213
|
-
Rails.application.secrets.api_key # ✅ Works
|
|
214
|
-
Rails.application.secrets.oauth_settings # ✅ Works
|
|
215
|
-
Rails::Secrets.load # ✅ Load default config/secrets.yml
|
|
216
|
-
Rails::Secrets.parse(['custom.yml'], env: Rails.env) # ✅ Parse custom files
|
|
217
|
-
```
|
|
78
|
+
Supports full ERB templating for environment variables:
|
|
218
79
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
80
|
+
```yaml
|
|
81
|
+
production:
|
|
82
|
+
api_key: <%= ENV['API_KEY'] %>
|
|
83
|
+
pool_size: <%= ENV.fetch('DB_POOL', '5').to_i %>
|
|
84
|
+
features:
|
|
85
|
+
enabled: <%= ENV.fetch('FEATURES_ON', 'false') == 'true' %>
|
|
86
|
+
hosts: <%= ENV.fetch('ALLOWED_HOSTS', 'localhost').split(',') %>
|
|
226
87
|
```
|
|
227
88
|
|
|
228
|
-
##
|
|
89
|
+
## Development Tools
|
|
229
90
|
|
|
230
|
-
|
|
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
|
|
91
|
+
Reload secrets in development:
|
|
236
92
|
|
|
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
93
|
```ruby
|
|
244
|
-
#
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
254
|
-
|
|
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') %>`
|
|
259
|
-
|
|
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
|
|
94
|
+
# Available after setup_multi_file!
|
|
95
|
+
reload_secrets!
|
|
96
|
+
Rails.application.reload_secrets!
|
|
280
97
|
```
|
|
281
98
|
|
|
282
99
|
## License
|
|
283
100
|
|
|
284
|
-
MIT
|
|
101
|
+
MIT
|
|
@@ -6,9 +6,9 @@ module Secvault
|
|
|
6
6
|
# This replicates the Rails < 7.2 Rails::Secrets module functionality
|
|
7
7
|
module RailsSecrets
|
|
8
8
|
extend self
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
# Parse secrets from one or more YAML files
|
|
11
|
-
#
|
|
11
|
+
#
|
|
12
12
|
# Supports:
|
|
13
13
|
# - ERB templating for environment variables
|
|
14
14
|
# - Shared sections that apply to all environments
|
|
@@ -19,26 +19,26 @@ module Secvault
|
|
|
19
19
|
# Examples:
|
|
20
20
|
# # Single file
|
|
21
21
|
# Rails::Secrets.parse(['config/secrets.yml'], env: 'development')
|
|
22
|
-
#
|
|
22
|
+
#
|
|
23
23
|
# # Multiple files (merged in order)
|
|
24
24
|
# Rails::Secrets.parse([
|
|
25
25
|
# 'config/secrets.yml',
|
|
26
26
|
# 'config/secrets.local.yml'
|
|
27
27
|
# ], env: 'development')
|
|
28
|
-
#
|
|
28
|
+
#
|
|
29
29
|
# # Load default config/secrets.yml
|
|
30
30
|
# Rails::Secrets.load # uses current Rails.env
|
|
31
31
|
# Rails::Secrets.load(env: 'production')
|
|
32
32
|
def parse(paths, env:)
|
|
33
33
|
Secvault::Secrets.parse(paths, env: env.to_s)
|
|
34
34
|
end
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
# Load secrets from the default config/secrets.yml file
|
|
37
37
|
def load(env: Rails.env)
|
|
38
38
|
secrets_path = Rails.root.join("config/secrets.yml")
|
|
39
39
|
parse([secrets_path], env: env)
|
|
40
40
|
end
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
# Backward compatibility aliases (deprecated)
|
|
43
43
|
alias_method :parse_default, :load
|
|
44
44
|
alias_method :read, :load
|
|
@@ -49,8 +49,8 @@ end
|
|
|
49
49
|
# Only for Rails 7.2+ where Rails::Secrets was removed
|
|
50
50
|
if defined?(Rails) && Rails.respond_to?(:version)
|
|
51
51
|
rails_version = Rails.version
|
|
52
|
-
major, minor = rails_version.split(
|
|
53
|
-
|
|
52
|
+
major, minor = rails_version.split(".").map(&:to_i)
|
|
53
|
+
|
|
54
54
|
# Only alias for Rails 7.2+ to avoid conflicts with native Rails::Secrets in 7.1
|
|
55
55
|
if major > 7 || (major == 7 && minor >= 2)
|
|
56
56
|
module Rails
|
data/lib/secvault/railtie.rb
CHANGED
|
@@ -9,16 +9,16 @@ module Secvault
|
|
|
9
9
|
initializer "secvault.initialize", before: :load_environment_hook do |app|
|
|
10
10
|
Secvault::Secrets.setup(app)
|
|
11
11
|
end
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
# Ensure initialization happens early in all environments
|
|
14
14
|
config.before_configuration do |app|
|
|
15
15
|
secrets_path = app.root.join("config/secrets.yml")
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
if secrets_path.exist? && !Rails.application.respond_to?(:secrets)
|
|
18
18
|
# Early initialization for test environment compatibility
|
|
19
|
-
current_env = ENV[
|
|
19
|
+
current_env = ENV["RAILS_ENV"] || "development"
|
|
20
20
|
secrets = Secvault::Secrets.read_secrets(secrets_path, current_env)
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
if secrets
|
|
23
23
|
Rails.application.define_singleton_method(:secrets) do
|
|
24
24
|
@secrets ||= begin
|
|
@@ -31,6 +31,5 @@ module Secvault
|
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
|
-
|
|
35
34
|
end
|
|
36
35
|
end
|
data/lib/secvault/secrets.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/core_ext/hash/keys"
|
|
4
|
+
require "active_support/core_ext/hash/deep_merge"
|
|
4
5
|
require "active_support/ordered_options"
|
|
5
6
|
require "pathname"
|
|
6
7
|
require "erb"
|
|
@@ -12,16 +13,16 @@ module Secvault
|
|
|
12
13
|
def setup(app)
|
|
13
14
|
# Only auto-setup for Rails 7.2+ where secrets functionality was removed
|
|
14
15
|
return unless rails_7_2_or_later?
|
|
15
|
-
|
|
16
|
+
|
|
16
17
|
secrets_path = app.root.join("config/secrets.yml")
|
|
17
18
|
|
|
18
19
|
if secrets_path.exist?
|
|
19
20
|
# Use a more reliable approach that works in all environments
|
|
20
21
|
app.config.before_configuration do
|
|
21
|
-
current_env = ENV[
|
|
22
|
+
current_env = ENV["RAILS_ENV"] || Rails.env || "development"
|
|
22
23
|
setup_secrets_immediately(app, secrets_path, current_env)
|
|
23
24
|
end
|
|
24
|
-
|
|
25
|
+
|
|
25
26
|
# Also try during to_prepare as a fallback
|
|
26
27
|
app.config.to_prepare do
|
|
27
28
|
current_env = Rails.env
|
|
@@ -31,14 +32,14 @@ module Secvault
|
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
# Manual setup method for Rails 7.1 (opt-in)
|
|
36
37
|
def setup_for_rails_71!(app)
|
|
37
38
|
secrets_path = app.root.join("config/secrets.yml")
|
|
38
39
|
|
|
39
40
|
if secrets_path.exist?
|
|
40
41
|
app.config.before_configuration do
|
|
41
|
-
current_env = ENV[
|
|
42
|
+
current_env = ENV["RAILS_ENV"] || Rails.env || "development"
|
|
42
43
|
setup_secrets_immediately(app, secrets_path, current_env)
|
|
43
44
|
end
|
|
44
45
|
end
|
|
@@ -60,7 +61,7 @@ module Secvault
|
|
|
60
61
|
end
|
|
61
62
|
end
|
|
62
63
|
end
|
|
63
|
-
|
|
64
|
+
|
|
64
65
|
# If secrets accessor already exists, merge the secrets
|
|
65
66
|
if Rails.application.respond_to?(:secrets) && Rails.application.secrets.respond_to?(:merge!)
|
|
66
67
|
Rails.application.secrets.merge!(secrets)
|
|
@@ -71,27 +72,23 @@ module Secvault
|
|
|
71
72
|
# Classic Rails::Secrets.parse implementation
|
|
72
73
|
# Parses plain YAML secrets files and merges shared + environment-specific sections
|
|
73
74
|
def parse(paths, env:)
|
|
74
|
-
paths.each_with_object(
|
|
75
|
+
paths.each_with_object({}) do |path, all_secrets|
|
|
75
76
|
# Handle string paths by converting to Pathname
|
|
76
77
|
path = Pathname.new(path) unless path.respond_to?(:exist?)
|
|
77
78
|
next unless path.exist?
|
|
78
|
-
|
|
79
|
+
|
|
79
80
|
# Read and process the plain YAML file content
|
|
80
81
|
source = path.read
|
|
81
|
-
|
|
82
|
+
|
|
82
83
|
# Process ERB and parse YAML
|
|
83
84
|
erb_result = ERB.new(source).result
|
|
84
|
-
secrets =
|
|
85
|
-
|
|
86
|
-
else
|
|
87
|
-
YAML.load(erb_result)
|
|
88
|
-
end
|
|
89
|
-
|
|
85
|
+
secrets = YAML.safe_load(erb_result, aliases: true, permitted_classes: [])
|
|
86
|
+
|
|
90
87
|
secrets ||= {}
|
|
91
|
-
|
|
92
|
-
# Merge shared secrets first, then environment-specific
|
|
93
|
-
all_secrets.
|
|
94
|
-
all_secrets.
|
|
88
|
+
|
|
89
|
+
# Merge shared secrets first, then environment-specific (using deep merge)
|
|
90
|
+
all_secrets.deep_merge!(secrets["shared"].deep_symbolize_keys) if secrets["shared"]
|
|
91
|
+
all_secrets.deep_merge!(secrets[env].deep_symbolize_keys) if secrets[env]
|
|
95
92
|
end
|
|
96
93
|
end
|
|
97
94
|
|
|
@@ -99,7 +96,7 @@ module Secvault
|
|
|
99
96
|
if secrets_path.exist?
|
|
100
97
|
# Handle plain YAML secrets.yml only
|
|
101
98
|
all_secrets = YAML.safe_load(ERB.new(secrets_path.read).result, aliases: true)
|
|
102
|
-
|
|
99
|
+
|
|
103
100
|
env_secrets = all_secrets[env.to_s]
|
|
104
101
|
return env_secrets.deep_symbolize_keys if env_secrets
|
|
105
102
|
end
|
|
@@ -108,10 +105,10 @@ module Secvault
|
|
|
108
105
|
end
|
|
109
106
|
|
|
110
107
|
private
|
|
111
|
-
|
|
108
|
+
|
|
112
109
|
def rails_7_2_or_later?
|
|
113
110
|
rails_version = Rails.version
|
|
114
|
-
major, minor = rails_version.split(
|
|
111
|
+
major, minor = rails_version.split(".").map(&:to_i)
|
|
115
112
|
major > 7 || (major == 7 && minor >= 2)
|
|
116
113
|
end
|
|
117
114
|
end
|
data/lib/secvault/version.rb
CHANGED
data/lib/secvault.rb
CHANGED
|
@@ -12,9 +12,9 @@ loader = Zeitwerk::Loader.for_gem
|
|
|
12
12
|
loader.setup
|
|
13
13
|
|
|
14
14
|
# Secvault - Simple secrets management for Rails
|
|
15
|
-
#
|
|
16
|
-
# Secvault restores the classic Rails secrets.yml functionality that was removed
|
|
17
|
-
# in Rails 7.2, using simple, plain YAML files for environment-specific secrets
|
|
15
|
+
#
|
|
16
|
+
# Secvault restores the classic Rails secrets.yml functionality that was removed
|
|
17
|
+
# in Rails 7.2, using simple, plain YAML files for environment-specific secrets
|
|
18
18
|
# management.
|
|
19
19
|
#
|
|
20
20
|
# ## Rails Version Support:
|
|
@@ -58,8 +58,21 @@ module Secvault
|
|
|
58
58
|
|
|
59
59
|
extend self
|
|
60
60
|
|
|
61
|
-
#
|
|
61
|
+
# Internal storage for loaded secrets
|
|
62
|
+
@@loaded_secrets = nil
|
|
63
|
+
|
|
64
|
+
# Access to loaded secrets without Rails integration
|
|
65
|
+
def secrets
|
|
66
|
+
@@loaded_secrets || ActiveSupport::OrderedOptions.new
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Check if Secvault is currently active (started)
|
|
62
70
|
def active?
|
|
71
|
+
@@loaded_secrets != nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Check if Secvault is integrated with Rails.application.secrets
|
|
75
|
+
def rails_integrated?
|
|
63
76
|
defined?(Rails) && Rails::Secrets == Secvault::RailsSecrets
|
|
64
77
|
end
|
|
65
78
|
|
|
@@ -69,7 +82,7 @@ module Secvault
|
|
|
69
82
|
require "secvault/railtie"
|
|
70
83
|
require "secvault/rails_secrets"
|
|
71
84
|
end
|
|
72
|
-
|
|
85
|
+
|
|
73
86
|
# Helper method to set up Secvault for older Rails versions
|
|
74
87
|
# This provides an easy way to integrate Secvault into older Rails apps
|
|
75
88
|
# that still have native Rails::Secrets functionality (like Rails 7.1).
|
|
@@ -87,24 +100,24 @@ module Secvault
|
|
|
87
100
|
Rails.send(:remove_const, :Secrets)
|
|
88
101
|
end
|
|
89
102
|
Rails.const_set(:Secrets, Secvault::RailsSecrets)
|
|
90
|
-
|
|
103
|
+
|
|
91
104
|
# Set up Rails.application.secrets replacement
|
|
92
105
|
Rails.application.config.after_initialize do
|
|
93
106
|
secrets_path = Rails.root.join("config/secrets.yml")
|
|
94
|
-
|
|
107
|
+
|
|
95
108
|
if secrets_path.exist?
|
|
96
109
|
# Load secrets using Secvault
|
|
97
110
|
loaded_secrets = Rails::Secrets.parse([secrets_path], env: Rails.env)
|
|
98
|
-
|
|
111
|
+
|
|
99
112
|
# Create ActiveSupport::OrderedOptions object for compatibility
|
|
100
113
|
secrets_object = ActiveSupport::OrderedOptions.new
|
|
101
114
|
secrets_object.merge!(loaded_secrets)
|
|
102
|
-
|
|
115
|
+
|
|
103
116
|
# Replace Rails.application.secrets
|
|
104
117
|
Rails.application.define_singleton_method(:secrets) do
|
|
105
118
|
secrets_object
|
|
106
119
|
end
|
|
107
|
-
|
|
120
|
+
|
|
108
121
|
# Log integration success (except in production)
|
|
109
122
|
unless Rails.env.production?
|
|
110
123
|
Rails.logger&.info "[Secvault] Rails 7.1 integration complete. Loaded #{loaded_secrets.keys.size} secret keys."
|
|
@@ -114,14 +127,14 @@ module Secvault
|
|
|
114
127
|
end
|
|
115
128
|
end
|
|
116
129
|
end
|
|
117
|
-
|
|
130
|
+
|
|
118
131
|
# Set up multi-file secrets loading with a clean API
|
|
119
132
|
# Just pass an array of file paths and Secvault handles the rest
|
|
120
133
|
#
|
|
121
134
|
# Usage in an initializer:
|
|
122
135
|
# Secvault.setup_multi_file!([
|
|
123
136
|
# 'config/secrets.yml',
|
|
124
|
-
# 'config/secrets.oauth.yml',
|
|
137
|
+
# 'config/secrets.oauth.yml',
|
|
125
138
|
# 'config/secrets.local.yml'
|
|
126
139
|
# ])
|
|
127
140
|
#
|
|
@@ -132,52 +145,84 @@ module Secvault
|
|
|
132
145
|
def setup_multi_file!(files, reload_method: Rails.env.development?, logger: !Rails.env.production?)
|
|
133
146
|
# Ensure Secvault integration is active
|
|
134
147
|
setup_backward_compatibility_with_older_rails! unless active?
|
|
135
|
-
|
|
148
|
+
|
|
136
149
|
# Convert strings to Pathname objects and resolve relative to Rails.root
|
|
137
150
|
file_paths = Array(files).map do |file|
|
|
138
151
|
file.is_a?(Pathname) ? file : Rails.root.join(file)
|
|
139
152
|
end
|
|
140
|
-
|
|
153
|
+
|
|
141
154
|
# Set up the multi-file loading
|
|
142
155
|
Rails.application.config.after_initialize do
|
|
143
156
|
load_multi_file_secrets!(file_paths, logger: logger)
|
|
144
157
|
end
|
|
145
|
-
|
|
158
|
+
|
|
146
159
|
# Add reload helper in development
|
|
147
160
|
if reload_method
|
|
148
161
|
add_reload_helper!(file_paths)
|
|
149
162
|
end
|
|
150
163
|
end
|
|
151
|
-
|
|
152
|
-
# Load secrets
|
|
164
|
+
|
|
165
|
+
# Load secrets into Secvault.secrets only (no Rails integration)
|
|
166
|
+
def load_secrets_only!(files, logger: !Rails.env.production?)
|
|
167
|
+
# Convert strings to Pathname objects and resolve relative to Rails.root
|
|
168
|
+
file_paths = Array(files).map do |file|
|
|
169
|
+
file.is_a?(Pathname) ? file : Rails.root.join(file)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
existing_files = file_paths.select(&:exist?)
|
|
173
|
+
|
|
174
|
+
if existing_files.any?
|
|
175
|
+
# Load and merge all secrets files using Secvault's parser directly
|
|
176
|
+
merged_secrets = Secvault::Secrets.parse(existing_files, env: Rails.env)
|
|
177
|
+
|
|
178
|
+
# Store in Secvault.secrets (ActiveSupport::OrderedOptions for compatibility)
|
|
179
|
+
@@loaded_secrets = ActiveSupport::OrderedOptions.new
|
|
180
|
+
@@loaded_secrets.merge!(merged_secrets)
|
|
181
|
+
|
|
182
|
+
# Log successful loading
|
|
183
|
+
if logger
|
|
184
|
+
file_names = existing_files.map(&:basename)
|
|
185
|
+
Rails.logger&.info "[Secvault] Loaded #{existing_files.size} files: #{file_names.join(", ")}"
|
|
186
|
+
Rails.logger&.info "[Secvault] Parsed #{merged_secrets.keys.size} secret keys for #{Rails.env}"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
true
|
|
190
|
+
else
|
|
191
|
+
Rails.logger&.warn "[Secvault] No secrets files found" if logger
|
|
192
|
+
@@loaded_secrets = ActiveSupport::OrderedOptions.new
|
|
193
|
+
false
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Load secrets from multiple files and merge them (with Rails integration)
|
|
153
198
|
def load_multi_file_secrets!(file_paths, logger: !Rails.env.production?)
|
|
154
199
|
existing_files = file_paths.select(&:exist?)
|
|
155
|
-
|
|
200
|
+
|
|
156
201
|
if existing_files.any?
|
|
157
202
|
# Load and merge all secrets files
|
|
158
203
|
merged_secrets = Rails::Secrets.parse(existing_files, env: Rails.env)
|
|
159
|
-
|
|
204
|
+
|
|
160
205
|
# Create ActiveSupport::OrderedOptions object for Rails compatibility
|
|
161
206
|
secrets_object = ActiveSupport::OrderedOptions.new
|
|
162
207
|
secrets_object.merge!(merged_secrets)
|
|
163
|
-
|
|
208
|
+
|
|
164
209
|
# Replace Rails.application.secrets
|
|
165
210
|
Rails.application.define_singleton_method(:secrets) { secrets_object }
|
|
166
|
-
|
|
211
|
+
|
|
167
212
|
# Log successful loading
|
|
168
213
|
if logger
|
|
169
214
|
file_names = existing_files.map(&:basename)
|
|
170
|
-
Rails.logger&.info "[Secvault Multi-File] Loaded #{existing_files.size} files: #{file_names.join(
|
|
215
|
+
Rails.logger&.info "[Secvault Multi-File] Loaded #{existing_files.size} files: #{file_names.join(", ")}"
|
|
171
216
|
Rails.logger&.info "[Secvault Multi-File] Merged #{merged_secrets.keys.size} secret keys for #{Rails.env}"
|
|
172
217
|
end
|
|
173
|
-
|
|
218
|
+
|
|
174
219
|
merged_secrets
|
|
175
220
|
else
|
|
176
221
|
Rails.logger&.warn "[Secvault Multi-File] No secrets files found" if logger
|
|
177
222
|
{}
|
|
178
223
|
end
|
|
179
224
|
end
|
|
180
|
-
|
|
225
|
+
|
|
181
226
|
# Add reload helper method for development
|
|
182
227
|
def add_reload_helper!(file_paths)
|
|
183
228
|
# Define reload method on Rails.application
|
|
@@ -186,13 +231,66 @@ module Secvault
|
|
|
186
231
|
puts "🔄 Reloaded secrets from #{file_paths.size} files"
|
|
187
232
|
true
|
|
188
233
|
end
|
|
189
|
-
|
|
234
|
+
|
|
190
235
|
# Also make it available as a top-level method
|
|
191
236
|
Object.define_method(:reload_secrets!) do
|
|
192
237
|
Rails.application.reload_secrets!
|
|
193
238
|
end
|
|
194
239
|
end
|
|
195
|
-
|
|
240
|
+
|
|
241
|
+
# Start Secvault and load secrets (without Rails integration)
|
|
242
|
+
#
|
|
243
|
+
# Usage:
|
|
244
|
+
# Secvault.start! # Uses config/secrets.yml only
|
|
245
|
+
# Secvault.start!(files: []) # Same as above
|
|
246
|
+
# Secvault.start!(files: ['path/to/secrets.yml']) # Custom single file
|
|
247
|
+
# Secvault.start!(files: ['gem.yml', 'app.yml']) # Multiple files
|
|
248
|
+
#
|
|
249
|
+
# Access loaded secrets via: Secvault.secrets.your_key
|
|
250
|
+
# To integrate with Rails.application.secrets, call: Secvault.integrate_with_rails!
|
|
251
|
+
#
|
|
252
|
+
# Options:
|
|
253
|
+
# - files: Array of file paths (String or Pathname). Defaults to ['config/secrets.yml']
|
|
254
|
+
# - logger: Enable logging (default: true except production)
|
|
255
|
+
def start!(files: [], logger: !Rails.env.production?)
|
|
256
|
+
# Default to host app's config/secrets.yml if no files specified
|
|
257
|
+
files_to_load = files.empty? ? ["config/secrets.yml"] : files
|
|
258
|
+
|
|
259
|
+
# Load secrets into Secvault.secrets (completely independent of Rails)
|
|
260
|
+
load_secrets_only!(files_to_load, logger: logger)
|
|
261
|
+
|
|
262
|
+
true
|
|
263
|
+
rescue => e
|
|
264
|
+
Rails.logger&.error "[Secvault] Failed to start: #{e.message}" if defined?(Rails)
|
|
265
|
+
false
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Integrate loaded secrets with Rails.application.secrets
|
|
269
|
+
def integrate_with_rails!
|
|
270
|
+
return false unless @@loaded_secrets
|
|
271
|
+
|
|
272
|
+
begin
|
|
273
|
+
# Set up Rails::Secrets to use Secvault's parser (only when integrating)
|
|
274
|
+
unless rails_integrated?
|
|
275
|
+
if defined?(Rails::Secrets)
|
|
276
|
+
Rails.send(:remove_const, :Secrets)
|
|
277
|
+
end
|
|
278
|
+
Rails.const_set(:Secrets, Secvault::RailsSecrets)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Replace Rails.application.secrets with Secvault's loaded secrets
|
|
282
|
+
Rails.application.define_singleton_method(:secrets) do
|
|
283
|
+
Secvault.secrets
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
Rails.logger&.info "[Secvault] Integrated with Rails.application.secrets" unless Rails.env.production?
|
|
287
|
+
true
|
|
288
|
+
rescue => e
|
|
289
|
+
Rails.logger&.error "[Secvault] Failed to integrate with Rails: #{e.message}" if defined?(Rails)
|
|
290
|
+
false
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
196
294
|
# Backward compatibility aliases
|
|
197
295
|
alias_method :setup_rails_71_integration!, :setup_backward_compatibility_with_older_rails!
|
|
198
296
|
alias_method :setup_multi_files!, :setup_multi_file! # Alternative name
|
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: 2.
|
|
4
|
+
version: 2.7.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-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -49,20 +49,14 @@ extra_rdoc_files: []
|
|
|
49
49
|
files:
|
|
50
50
|
- ".rspec"
|
|
51
51
|
- ".standard.yml"
|
|
52
|
-
- CHANGELOG.md
|
|
53
|
-
- CODE_OF_CONDUCT.md
|
|
54
52
|
- LICENSE.txt
|
|
55
53
|
- README.md
|
|
56
54
|
- Rakefile
|
|
57
55
|
- lib/secvault.rb
|
|
58
|
-
- lib/secvault/generators/secrets_generator.rb
|
|
59
56
|
- lib/secvault/rails_secrets.rb
|
|
60
57
|
- lib/secvault/railtie.rb
|
|
61
58
|
- lib/secvault/secrets.rb
|
|
62
|
-
- lib/secvault/secrets_helper.rb
|
|
63
59
|
- lib/secvault/version.rb
|
|
64
|
-
- secvault-2.0.0.gem
|
|
65
|
-
- sig/secvault.rbs
|
|
66
60
|
homepage: https://github.com/unnitallman/secvault
|
|
67
61
|
licenses:
|
|
68
62
|
- MIT
|
data/CHANGELOG.md
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
## [Unreleased]
|
|
2
|
-
|
|
3
|
-
## [2.3.0] - 2025-09-22
|
|
4
|
-
|
|
5
|
-
### Changed
|
|
6
|
-
|
|
7
|
-
- **Better method naming**: Renamed `setup_rails_71_integration!` to `setup_backward_compatibility_with_older_rails!`
|
|
8
|
-
- **More generic approach**: New method name works for any older Rails version, not just 7.1
|
|
9
|
-
- **Updated documentation**: README now uses "Older Rails Integration" instead of "Rails 7.1 Integration"
|
|
10
|
-
- **Clearer version support**: Documentation now shows "Rails 7.1 and older" for better clarity
|
|
11
|
-
|
|
12
|
-
### Backward Compatibility
|
|
13
|
-
|
|
14
|
-
- ✅ **Old method name still works**: `setup_rails_71_integration!` is aliased to the new method
|
|
15
|
-
- ✅ **No breaking changes**: All existing code continues to work
|
|
16
|
-
- ✅ **Updated test apps**: Rails 7.1 test app uses the new, cleaner method name
|
|
17
|
-
|
|
18
|
-
### Benefits
|
|
19
|
-
|
|
20
|
-
- **Future-proof naming**: Works for Rails 7.1, 7.0, 6.x, or any version with native secrets
|
|
21
|
-
- **Clearer intent**: Method name clearly indicates it's for backward compatibility
|
|
22
|
-
- **Better documentation**: More generic approach in README and code comments
|
|
23
|
-
- **Maintained compatibility**: Existing users don't need to change anything
|
|
24
|
-
|
|
25
|
-
## [2.2.0] - 2025-09-22
|
|
26
|
-
|
|
27
|
-
### Added
|
|
28
|
-
|
|
29
|
-
- **New simplified API**: `Rails::Secrets.load()` - cleaner method to load default config/secrets.yml
|
|
30
|
-
- **Enhanced README** with comprehensive examples for multiple files usage
|
|
31
|
-
- **Better documentation** showing how to parse custom files and multiple file merging
|
|
32
|
-
- **Backward compatibility aliases** - `parse_default` and `read` still work
|
|
33
|
-
|
|
34
|
-
### Changed
|
|
35
|
-
|
|
36
|
-
- **Improved method naming**: `Rails::Secrets.load()` is now the preferred method over `parse_default()`
|
|
37
|
-
- **Enhanced documentation** in code with clear examples for single file, multiple files, and custom paths
|
|
38
|
-
- **Better README examples** showing advanced usage patterns
|
|
39
|
-
|
|
40
|
-
### Examples Added
|
|
41
|
-
|
|
42
|
-
- Multiple secrets files merging: `Rails::Secrets.parse(['secrets.yml', 'secrets.local.yml'], env: Rails.env)`
|
|
43
|
-
- Environment-specific loading: `Rails::Secrets.load(env: 'production')`
|
|
44
|
-
- Custom file parsing: `Rails::Secrets.parse(['config/custom.yml'], env: Rails.env)`
|
|
45
|
-
- Multiple path support: `Rails::Secrets.parse([Rails.root.join('config', 'secrets.yml')], env: Rails.env)`
|
|
46
|
-
|
|
47
|
-
### Backward Compatibility
|
|
48
|
-
|
|
49
|
-
- ✅ All existing methods still work
|
|
50
|
-
- ✅ `parse_default` → `load` (alias maintained)
|
|
51
|
-
- ✅ `read` → `load` (alias maintained)
|
|
52
|
-
- ✅ No breaking changes
|
|
53
|
-
|
|
54
|
-
## [2.1.0] - 2025-09-22
|
|
55
|
-
|
|
56
|
-
### Removed
|
|
57
|
-
|
|
58
|
-
- **Removed all rake tasks** - Ultimate simplicity! No more `rake secvault:setup`, `rake secvault:edit`, or `rake secvault:show`
|
|
59
|
-
- Removed `lib/secvault/tasks.rake` file entirely
|
|
60
|
-
- Removed rake task loading from railtie
|
|
61
|
-
|
|
62
|
-
### Changed
|
|
63
|
-
|
|
64
|
-
- **Ultra-simple setup**: Just create `config/secrets.yml` with any text editor
|
|
65
|
-
- Updated README to reflect manual file creation instead of rake tasks
|
|
66
|
-
- Updated module documentation to show simple 3-step process
|
|
67
|
-
- Cleaner railtie without task loading complexity
|
|
68
|
-
|
|
69
|
-
### Benefits
|
|
70
|
-
|
|
71
|
-
- **Zero dependencies on rake tasks** - works with just plain YAML files
|
|
72
|
-
- **Even simpler** - no commands to remember, just edit YAML files
|
|
73
|
-
- **More intuitive** - developers already know how to create and edit YAML files
|
|
74
|
-
- **Less code** - removed unnecessary complexity
|
|
75
|
-
|
|
76
|
-
### Tested
|
|
77
|
-
|
|
78
|
-
- ✅ Rails 7.1 integration works perfectly
|
|
79
|
-
- ✅ Rails 8.0 automatic setup works perfectly
|
|
80
|
-
- ✅ No rake task conflicts or errors
|
|
81
|
-
|
|
82
|
-
## [2.0.0] - 2025-09-22
|
|
83
|
-
|
|
84
|
-
### BREAKING CHANGES
|
|
85
|
-
|
|
86
|
-
- **Removed all encryption functionality** - Secvault now focuses purely on plain YAML secrets management
|
|
87
|
-
- Removed ActiveSupport::EncryptedFile dependencies
|
|
88
|
-
- Removed MissingKeyError and InvalidKeyError exceptions
|
|
89
|
-
- Removed `encrypted?`, `decrypt`, `decrypt_secrets` methods
|
|
90
|
-
- Simplified rake tasks to work with plain YAML only
|
|
91
|
-
|
|
92
|
-
### Added
|
|
93
|
-
|
|
94
|
-
- Simplified `rake secvault:setup` that creates plain YAML files with helpful comments
|
|
95
|
-
- Better error messages and user guidance in rake tasks
|
|
96
|
-
- Cleaner, more focused codebase without encryption complexity
|
|
97
|
-
|
|
98
|
-
### Changed
|
|
99
|
-
|
|
100
|
-
- **Major simplification**: All secrets are now stored in plain YAML files
|
|
101
|
-
- Updated README to reflect plain YAML approach
|
|
102
|
-
- Updated module documentation and gemspec descriptions
|
|
103
|
-
- Rake tasks now use emojis and better user experience
|
|
104
|
-
- Production secrets should use ERB syntax with environment variables
|
|
105
|
-
|
|
106
|
-
### Benefits
|
|
107
|
-
|
|
108
|
-
- Much simpler gem with single focus: plain YAML secrets management
|
|
109
|
-
- No encryption keys to manage or lose
|
|
110
|
-
- Easy to understand, edit, and debug secrets files
|
|
111
|
-
- Perfect for development and test environments
|
|
112
|
-
- Production secrets via environment variables (recommended best practice)
|
|
113
|
-
|
|
114
|
-
## [1.0.4] - 2025-09-22
|
|
115
|
-
|
|
116
|
-
### Added
|
|
117
|
-
|
|
118
|
-
- Comprehensive Rails 7.1 integration support
|
|
119
|
-
- New `Secvault.setup_rails_71_integration!` helper method for easy Rails 7.1 setup
|
|
120
|
-
- Enhanced documentation with Rails 7.1 integration guide
|
|
121
|
-
- Module-level documentation with usage examples and version compatibility
|
|
122
|
-
|
|
123
|
-
### Improved
|
|
124
|
-
|
|
125
|
-
- Better Rails 7.1 compatibility with automatic detection and setup
|
|
126
|
-
- Enhanced README with Rails 7.1 integration section
|
|
127
|
-
- Improved error handling and logging for Rails 7.1 integration
|
|
128
|
-
- More comprehensive inline documentation
|
|
129
|
-
|
|
130
|
-
### Changed
|
|
131
|
-
|
|
132
|
-
- Refined automatic setup logic to avoid conflicts with Rails 7.1 native functionality
|
|
133
|
-
- Updated gemspec description to include Rails 7.1+ support
|
|
134
|
-
|
|
135
|
-
## [1.0.3] - 2025-09-22
|
|
136
|
-
|
|
137
|
-
### Fixed
|
|
138
|
-
|
|
139
|
-
- Rails 7.1 compatibility issues with native Rails::Secrets conflicts
|
|
140
|
-
- String path handling in parse method
|
|
141
|
-
- Zeitwerk constant name mismatch resolution
|
|
142
|
-
|
|
143
|
-
### Added
|
|
144
|
-
|
|
145
|
-
- Manual setup method for Rails 7.1 (opt-in)
|
|
146
|
-
- Rails version detection for automatic setup decisions
|
|
147
|
-
- Only create Rails::Secrets alias for Rails 7.2+ to avoid conflicts
|
|
148
|
-
|
|
149
|
-
## [1.0.2] - 2025-09-22
|
|
150
|
-
|
|
151
|
-
### Changed
|
|
152
|
-
|
|
153
|
-
- Updated Rails dependency from >= 7.2.0 to >= 7.1.0 for broader compatibility
|
|
154
|
-
- Updated gem description to include Rails 7.1+ support
|
|
155
|
-
|
|
156
|
-
## [1.0.1] - 2025-09-22
|
|
157
|
-
|
|
158
|
-
### Fixed
|
|
159
|
-
|
|
160
|
-
- Zeitwerk constant name mismatch in rails_secrets.rb
|
|
161
|
-
- Changed module definition from Rails::Secrets to Secvault::RailsSecrets
|
|
162
|
-
- Added Rails::Secrets alias for backward compatibility
|
|
163
|
-
- Resolved Zeitwerk::NameError when loading Rails applications
|
|
164
|
-
|
|
165
|
-
## [1.0.0] - 2025-09-22
|
|
166
|
-
|
|
167
|
-
### Added
|
|
168
|
-
|
|
169
|
-
- Initial release of Secvault gem
|
|
170
|
-
- Rails secrets.yml functionality for Rails 7.2+
|
|
171
|
-
- Encrypted secrets.yml support using Rails' built-in encryption
|
|
172
|
-
- Environment-specific secrets management
|
|
173
|
-
- ERB template support in secrets files
|
|
174
|
-
- Rake tasks for secrets management:
|
|
175
|
-
- `rake secvault:setup` - Create encrypted secrets file
|
|
176
|
-
- `rake secvault:edit` - Edit encrypted secrets
|
|
177
|
-
- `rake secvault:show` - Display decrypted secrets
|
|
178
|
-
- Rails generator for creating secrets files
|
|
179
|
-
- Automatic integration with Rails.application.secrets
|
|
180
|
-
- Support for both encrypted and plain YAML secrets files
|
|
181
|
-
- Key management with config/secrets.yml.key
|
|
182
|
-
- Environment variable fallback for encryption key
|
|
183
|
-
- Comprehensive error handling for missing/invalid keys
|
|
184
|
-
- Full test coverage with RSpec
|
|
185
|
-
- Detailed documentation and usage examples
|
data/CODE_OF_CONDUCT.md
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# Contributor Covenant Code of Conduct
|
|
2
|
-
|
|
3
|
-
## Our Pledge
|
|
4
|
-
|
|
5
|
-
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
-
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
-
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
-
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
-
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
-
identity and orientation.
|
|
11
|
-
|
|
12
|
-
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
-
diverse, inclusive, and healthy community.
|
|
14
|
-
|
|
15
|
-
## Our Standards
|
|
16
|
-
|
|
17
|
-
Examples of behavior that contributes to a positive environment for our
|
|
18
|
-
community include:
|
|
19
|
-
|
|
20
|
-
* Demonstrating empathy and kindness toward other people
|
|
21
|
-
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
-
* Giving and gracefully accepting constructive feedback
|
|
23
|
-
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
-
and learning from the experience
|
|
25
|
-
* Focusing on what is best not just for us as individuals, but for the overall
|
|
26
|
-
community
|
|
27
|
-
|
|
28
|
-
Examples of unacceptable behavior include:
|
|
29
|
-
|
|
30
|
-
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
31
|
-
any kind
|
|
32
|
-
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
-
* Public or private harassment
|
|
34
|
-
* Publishing others' private information, such as a physical or email address,
|
|
35
|
-
without their explicit permission
|
|
36
|
-
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
-
professional setting
|
|
38
|
-
|
|
39
|
-
## Enforcement Responsibilities
|
|
40
|
-
|
|
41
|
-
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
-
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
-
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
-
or harmful.
|
|
45
|
-
|
|
46
|
-
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
-
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
-
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
-
decisions when appropriate.
|
|
50
|
-
|
|
51
|
-
## Scope
|
|
52
|
-
|
|
53
|
-
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
-
an individual is officially representing the community in public spaces.
|
|
55
|
-
Examples of representing our community include using an official email address,
|
|
56
|
-
posting via an official social media account, or acting as an appointed
|
|
57
|
-
representative at an online or offline event.
|
|
58
|
-
|
|
59
|
-
## Enforcement
|
|
60
|
-
|
|
61
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
-
reported to the community leaders responsible for enforcement at
|
|
63
|
-
[INSERT CONTACT METHOD].
|
|
64
|
-
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
-
|
|
66
|
-
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
-
reporter of any incident.
|
|
68
|
-
|
|
69
|
-
## Enforcement Guidelines
|
|
70
|
-
|
|
71
|
-
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
-
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
-
|
|
74
|
-
### 1. Correction
|
|
75
|
-
|
|
76
|
-
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
-
unprofessional or unwelcome in the community.
|
|
78
|
-
|
|
79
|
-
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
-
clarity around the nature of the violation and an explanation of why the
|
|
81
|
-
behavior was inappropriate. A public apology may be requested.
|
|
82
|
-
|
|
83
|
-
### 2. Warning
|
|
84
|
-
|
|
85
|
-
**Community Impact**: A violation through a single incident or series of
|
|
86
|
-
actions.
|
|
87
|
-
|
|
88
|
-
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
-
interaction with the people involved, including unsolicited interaction with
|
|
90
|
-
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
-
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
-
like social media. Violating these terms may lead to a temporary or permanent
|
|
93
|
-
ban.
|
|
94
|
-
|
|
95
|
-
### 3. Temporary Ban
|
|
96
|
-
|
|
97
|
-
**Community Impact**: A serious violation of community standards, including
|
|
98
|
-
sustained inappropriate behavior.
|
|
99
|
-
|
|
100
|
-
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
-
communication with the community for a specified period of time. No public or
|
|
102
|
-
private interaction with the people involved, including unsolicited interaction
|
|
103
|
-
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
-
Violating these terms may lead to a permanent ban.
|
|
105
|
-
|
|
106
|
-
### 4. Permanent Ban
|
|
107
|
-
|
|
108
|
-
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
-
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
-
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
-
|
|
112
|
-
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
113
|
-
community.
|
|
114
|
-
|
|
115
|
-
## Attribution
|
|
116
|
-
|
|
117
|
-
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
-
version 2.1, available at
|
|
119
|
-
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
120
|
-
|
|
121
|
-
Community Impact Guidelines were inspired by
|
|
122
|
-
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
123
|
-
|
|
124
|
-
For answers to common questions about this code of conduct, see the FAQ at
|
|
125
|
-
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
126
|
-
[https://www.contributor-covenant.org/translations][translations].
|
|
127
|
-
|
|
128
|
-
[homepage]: https://www.contributor-covenant.org
|
|
129
|
-
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
130
|
-
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
131
|
-
[FAQ]: https://www.contributor-covenant.org/faq
|
|
132
|
-
[translations]: https://www.contributor-covenant.org/translations
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rails/generators/base"
|
|
4
|
-
require "active_support/encrypted_file"
|
|
5
|
-
require "securerandom"
|
|
6
|
-
|
|
7
|
-
module Secvault
|
|
8
|
-
module Generators
|
|
9
|
-
class SecretsGenerator < Rails::Generators::Base
|
|
10
|
-
source_root File.expand_path("templates", __dir__)
|
|
11
|
-
|
|
12
|
-
desc "Creates a secrets.yml file for secrets management"
|
|
13
|
-
|
|
14
|
-
class_option :force, type: :boolean, default: false, desc: "Overwrite existing secrets.yml"
|
|
15
|
-
class_option :encrypted, type: :boolean, default: false, desc: "Create encrypted secrets.yml (default: plain YAML)"
|
|
16
|
-
|
|
17
|
-
def create_secrets_file
|
|
18
|
-
secrets_path = Rails.root.join("config/secrets.yml")
|
|
19
|
-
key_path = Rails.root.join("config/secrets.yml.key")
|
|
20
|
-
|
|
21
|
-
if secrets_path.exist? && !options[:force]
|
|
22
|
-
say "Secrets file already exists at config/secrets.yml", :yellow
|
|
23
|
-
return
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
default_content = generate_default_secrets
|
|
27
|
-
|
|
28
|
-
if options[:encrypted]
|
|
29
|
-
# Create encrypted secrets file
|
|
30
|
-
unless key_path.exist?
|
|
31
|
-
key = ActiveSupport::EncryptedFile.generate_key
|
|
32
|
-
File.write(key_path, key)
|
|
33
|
-
say "Generated encryption key in config/secrets.yml.key", :green
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
encrypted_file = ActiveSupport::EncryptedFile.new(
|
|
37
|
-
content_path: secrets_path,
|
|
38
|
-
key_path: key_path,
|
|
39
|
-
env_key: "RAILS_SECRETS_KEY",
|
|
40
|
-
raise_if_missing_key: true
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
encrypted_file.write(default_content)
|
|
44
|
-
say "Created encrypted secrets.yml file", :green
|
|
45
|
-
say "Add config/secrets.yml.key to your .gitignore file", :yellow
|
|
46
|
-
else
|
|
47
|
-
# Create plain YAML secrets file
|
|
48
|
-
File.write(secrets_path, default_content)
|
|
49
|
-
say "Created plain secrets.yml file", :green
|
|
50
|
-
say "Remember to add config/secrets.yml to your .gitignore if it contains sensitive data", :yellow
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def add_to_gitignore
|
|
55
|
-
gitignore_path = Rails.root.join(".gitignore")
|
|
56
|
-
key_entry = "/config/secrets.yml.key"
|
|
57
|
-
|
|
58
|
-
if gitignore_path.exist?
|
|
59
|
-
gitignore_content = File.read(gitignore_path)
|
|
60
|
-
unless gitignore_content.include?(key_entry)
|
|
61
|
-
File.open(gitignore_path, "a") do |f|
|
|
62
|
-
f.puts "\n# Ignore encrypted secrets key"
|
|
63
|
-
f.puts key_entry
|
|
64
|
-
end
|
|
65
|
-
say "Added secrets key to .gitignore", :green
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
private
|
|
71
|
-
|
|
72
|
-
def generate_default_secrets
|
|
73
|
-
<<~YAML
|
|
74
|
-
# Be sure to restart your server when you modify this file.
|
|
75
|
-
|
|
76
|
-
# Your secret key is used for verifying the integrity of signed cookies.
|
|
77
|
-
# If you change this key, all old signed cookies will become invalid!
|
|
78
|
-
|
|
79
|
-
# Make sure the secret is at least 30 characters and all random,
|
|
80
|
-
# no regular words or you'll be exposed to dictionary attacks.
|
|
81
|
-
# You can use `rails secret` to generate a secure secret key.
|
|
82
|
-
|
|
83
|
-
# Make sure the secrets in this file are kept private
|
|
84
|
-
# if you're sharing your code publicly.
|
|
85
|
-
|
|
86
|
-
development:
|
|
87
|
-
secret_key_base: #{SecureRandom.hex(64)}
|
|
88
|
-
|
|
89
|
-
test:
|
|
90
|
-
secret_key_base: #{SecureRandom.hex(64)}
|
|
91
|
-
|
|
92
|
-
# Do not keep production secrets in the repository,
|
|
93
|
-
# instead read values from the environment.
|
|
94
|
-
production:
|
|
95
|
-
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
|
96
|
-
YAML
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Secvault
|
|
4
|
-
class SecretsHelper
|
|
5
|
-
def initialize(app)
|
|
6
|
-
@app = app
|
|
7
|
-
@secrets_path = app.root.join("config/secrets.yml")
|
|
8
|
-
@key_path = app.root.join("config/secrets.yml.key")
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def secrets
|
|
12
|
-
@secrets ||= load_secrets
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def [](key)
|
|
16
|
-
secrets[key.to_sym]
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def fetch(key, default = nil)
|
|
20
|
-
secrets.fetch(key.to_sym, default)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def key?(key)
|
|
24
|
-
secrets.key?(key.to_sym)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def empty?
|
|
28
|
-
secrets.empty?
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def to_h
|
|
32
|
-
secrets.dup
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def load_secrets
|
|
38
|
-
return {} unless @secrets_path.exist?
|
|
39
|
-
|
|
40
|
-
env_secrets = Secvault::Secrets.read_secrets(@secrets_path, @key_path, Rails.env)
|
|
41
|
-
env_secrets || {}
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
data/secvault-2.0.0.gem
DELETED
|
Binary file
|
data/sig/secvault.rbs
DELETED