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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 441940b63b8d897f1ad3b84bf43168d20dcbf62017a604e201538cee0315a7ca
4
- data.tar.gz: fdd75a75105b0c34efb26096499ec6ffeac7340a170d4f8fcb7188c3f0d4e3db
3
+ metadata.gz: 7aa65a706270754c5654d4ddac89114d5c6f59f80c2bd1480d11dc0b350a57d9
4
+ data.tar.gz: 9ce9a4d9661505ba37a8c827d8f5811c8b9ff0b2fab8f8a8b89bb9f53fb62618
5
5
  SHA512:
6
- metadata.gz: 14fc56f53ff8729262cb5fa07bf2de96e908d7c4f6b00a1134ea2c86b3a433b220a24b3399ad43e97846cadd0bc79546780a4e0fce5318448497b62d5fba0714
7
- data.tar.gz: 3e4c8387fa38263e3bf99436211b3eaadf6b3d7d2aa5ec483cc6b19fc9dcbcb9bd402b9099e800fcc8c6fb9fc09b8281875415fdd90570fbee6625d324706032
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
- api_key: dev_key
41
- database_password: dev_password
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
- database_password: <%= ENV['DATABASE_PASSWORD'] %>
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
- **Production:** Use environment variables in your YAML:
49
- ```yaml
50
- production:
51
- api_key: <%= ENV['API_KEY'] %>
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
- **Multiple secrets files (merged in order):**
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.local.yml',
62
- 'config/secrets.production.yml'
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
- **Custom files:**
152
+ **Environment-specific loading:**
76
153
  ```ruby
77
- # Parse a custom secrets file
78
- custom_secrets = Rails::Secrets.parse(['config/custom.yml'], env: Rails.env)
154
+ # Load production secrets in any environment
155
+ production_secrets = Rails::Secrets.load(env: 'production')
79
156
 
80
- # Parse from different paths
81
- all_secrets = Rails::Secrets.parse([
82
- Rails.root.join('config', 'secrets.yml'),
83
- Rails.root.join('config', 'deploy', 'secrets.yml')
84
- ], env: Rails.env)
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
- ## Security
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
- ⚠️ Never commit production secrets to version control
109
- Use environment variables for production secrets with ERB syntax: `<%= ENV['SECRET'] %>`
110
- ✅ Add `config/secrets.yml` to `.gitignore` if it contains sensitive data
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Secvault
4
- VERSION = "2.3.0"
4
+ VERSION = "2.5.0"
5
5
  end
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
- # Backward compatibility alias
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)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secvault
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Unnikrishnan KP