secvault 2.6.0 → 2.7.1

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: 804b4325743910209d494c3c81b4f5f0dc8cf89dd802b4b424544923e42cee34
4
- data.tar.gz: 6c456cdd91bf12b27ab70922deb6736c42c8afdeb3e91c7173428c791441ff2c
3
+ metadata.gz: 3f3f850252ea738c99ee2fd7bb7c2e9a2688082abc78245f0ac1ae4f262c9f57
4
+ data.tar.gz: 95c9d1b54bdf4fdeaf490e7bbf4bd1bb13411d50ce9a11291f89848049258636
5
5
  SHA512:
6
- metadata.gz: 82efdd937655a3ba4eaefe8d732df4e2def7fd12a91302b182d1c300f1a17c10031339a53efe14af1f7c23bdfc40bdc44d43f578bc487a4e425551b75169f1c0
7
- data.tar.gz: 0bf0eab87edf50ef01b0d5f0989d8403cd2b96795a393504de87875541ef458fbe3342ba2d8d0e8316b252259bbfee00abff8d1cb265bf0d73cdd951e979c27a
6
+ metadata.gz: a2399e023247f056496230dc96da152b5c9fcdd1db98b5f7359a5406f9ce45dc190ce0c93cd5dc4736abb16e7396043d78482b0879785faa60353fe0a1d773dd
7
+ data.tar.gz: e3deef79404b1c382c2e6167e6cb2b2981f511b6e9058fdf4fc484d8b9fce0f041551121631165f60f2220ee1d3be0ef444fb12296dac803a58afb4800b18ce0
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
1
  --format documentation
2
2
  --color
3
- --require spec_helper
3
+ --require spec_helper
data/README.md CHANGED
@@ -1,10 +1,11 @@
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 with powerful features like YAML defaults, ERB interpolation, and multi-file configurations.
3
+ Restores Rails `secrets.yml` functionality for environment-specific secrets management using YAML files with ERB templating.
4
4
 
5
- **Rails Version Support:**
6
- - **Rails 7.1 and older**: Manual setup (see Older Rails Integration below)
7
- - **Rails 7.2+**: Automatic setup
5
+ ## Rails Version Support
6
+
7
+ - **Rails 7.2+**: Automatic setup (drop-in replacement for removed functionality)
8
+ - **Rails 7.1**: Manual setup required
8
9
  - **Rails 8.0+**: Full compatibility
9
10
 
10
11
  ## Installation
@@ -14,202 +15,87 @@ Restores the classic Rails `secrets.yml` functionality that was removed in Rails
14
15
  gem 'secvault'
15
16
  ```
16
17
 
17
- ```bash
18
- bundle install
19
- ```
20
-
21
- ## Quick Start (Rails 7.2+)
22
-
23
- ```bash
24
- # 1. Create secrets.yml
25
- touch config/secrets.yml
18
+ ## Quick Start
26
19
 
27
- # 2. Edit with your favorite editor
28
- $EDITOR config/secrets.yml
29
- ```
30
-
31
- **Usage in your app:**
32
- ```ruby
33
- Rails.application.secrets.api_key
34
- Rails.application.secrets.database_password
35
- ```
20
+ Create `config/secrets.yml`:
36
21
 
37
- **Example secrets.yml with YAML defaults and ERB:**
38
22
  ```yaml
39
- # YAML defaults - inherited by all environments
40
- default: &default
41
- app_name: "My Application"
42
- database:
43
- adapter: "postgresql"
44
- pool: 5
45
- timeout: 5000
46
- api:
47
- timeout: 30
48
- retries: 3
49
-
50
23
  development:
51
- <<: *default # Inherit defaults
52
- api_key: "dev_api_key_123"
53
- database:
54
- host: "localhost"
55
- name: "myapp_development"
56
- api:
57
- base_url: "http://localhost:3000" # Override default
24
+ api_key: "dev_key_123"
25
+ database_url: "postgresql://localhost/myapp_dev"
58
26
 
59
27
  production:
60
- <<: *default # Inherit defaults
61
28
  api_key: <%= ENV['API_KEY'] %>
62
- database:
63
- host: <%= ENV['DATABASE_HOST'] %>
64
- name: <%= ENV['DATABASE_NAME'] %>
65
- pool: <%= ENV.fetch('DATABASE_POOL', '10').to_i %> # Type conversion
66
- api:
67
- base_url: <%= ENV['API_BASE_URL'] %>
68
-
69
- # Boolean conversion
70
- features:
71
- new_ui: <%= ENV.fetch('FEATURE_NEW_UI', 'true') == 'true' %>
72
-
73
- # Array conversion
74
- oauth_scopes: <%= ENV.fetch('OAUTH_SCOPES', 'email,profile').split(',') %>
29
+ database_url: <%= ENV['DATABASE_URL'] %>
75
30
  ```
76
31
 
77
- ## Multi-File Configuration
78
-
79
- Organize secrets across multiple files with a **super clean API**:
32
+ Access secrets in your app:
80
33
 
81
34
  ```ruby
82
- # config/initializers/secvault.rb
83
- require "secvault"
84
-
85
- # That's it! Just pass your files array
86
- Secvault.setup_multi_file!([
87
- 'config/secrets.yml', # Base secrets
88
- 'config/secrets.oauth.yml', # OAuth & APIs
89
- 'config/secrets.local.yml' # Local overrides
90
- ])
35
+ Rails.application.secrets.api_key
36
+ Rails.application.secrets.database_url
91
37
  ```
92
38
 
93
- **What this does:**
94
- - ✅ Loads and merges all files in order (later files override earlier ones)
95
- - ✅ Handles missing files gracefully
96
- - ✅ Creates Rails.application.secrets with merged configuration
97
-
98
- **Advanced options:**
99
- ```ruby
100
- # Disable reload helper or logging
101
- Secvault.setup_multi_file!(files, reload_method: false, logger: false)
102
-
103
- # Use Pathname objects if needed
104
- Secvault.setup_multi_file!([
105
- Rails.root.join('config', 'secrets.yml'),
106
- Rails.root.join('config', 'secrets.oauth.yml')
107
- ])
108
- ```
39
+ ## Multi-File Configuration
109
40
 
110
- ## Advanced Usage
41
+ Load and merge multiple secrets files:
111
42
 
112
- **Manual multi-file parsing:**
113
43
  ```ruby
114
- # Parse multiple files - later files override earlier ones
115
- secrets = Rails::Secrets.parse([
44
+ # config/initializers/secvault.rb
45
+ Secvault.setup_multi_file!([
116
46
  'config/secrets.yml',
117
47
  'config/secrets.oauth.yml',
118
48
  'config/secrets.local.yml'
119
- ], env: Rails.env)
49
+ ])
120
50
  ```
121
51
 
122
- **Load specific environment:**
123
- ```ruby
124
- # Load production secrets in any environment
125
- production_secrets = Rails::Secrets.load(env: 'production')
52
+ Files are merged in order with deep merge support for nested hashes.
126
53
 
127
- # Load development secrets
128
- dev_secrets = Rails::Secrets.load(env: 'development')
129
- ```
54
+ ## Manual API
130
55
 
131
- **Environment-specific loading:**
132
56
  ```ruby
133
- # Load production secrets in any environment
134
- production_secrets = Rails::Secrets.load(env: 'production')
135
-
136
- # Load development secrets
137
- dev_secrets = Rails::Secrets.load(env: 'development')
138
- ```
57
+ # Parse specific files
58
+ secrets = Rails::Secrets.parse(['config/secrets.yml'], env: Rails.env)
139
59
 
140
- ## ERB Features & Type Conversion
60
+ # Load default config/secrets.yml
61
+ secrets = Rails::Secrets.load(env: 'production')
141
62
 
142
- Secvault supports ERB templating with automatic type conversion:
143
-
144
- ```yaml
145
- production:
146
- # String interpolation
147
- api_key: <%= ENV['API_KEY'] %>
148
-
149
- # Integer conversion
150
- database_pool: <%= ENV.fetch('DB_POOL', '10').to_i %>
151
-
152
- # Boolean conversion
153
- debug_enabled: <%= ENV.fetch('DEBUG', 'false') == 'true' %>
154
-
155
- # Array conversion
156
- allowed_hosts: <%= ENV.fetch('HOSTS', 'localhost,127.0.0.1').split(',') %>
157
-
158
- # Fallback values
159
- timeout: <%= ENV.fetch('TIMEOUT', '30').to_i %>
160
- adapter: <%= ENV.fetch('DB_ADAPTER', 'postgresql') %>
63
+ # Check if active
64
+ Secvault.active? # => true/false
161
65
  ```
162
66
 
163
- ## Development Tools
67
+ ## Rails 7.1 Integration
164
68
 
165
- **Hot-reload secrets (automatically available in development):**
166
- ```ruby
167
- # In Rails console - automatically added by setup_multi_file!
168
- reload_secrets! # Reloads all configured files without server restart
169
- # 🔄 Reloaded secrets from 3 files
69
+ For Rails 7.1 with existing secrets functionality:
170
70
 
171
- # Also available as:
172
- Rails.application.reload_secrets!
173
- ```
174
-
175
- **Check integration status:**
176
71
  ```ruby
177
- Secvault.active? # Returns true if Secvault is managing secrets
72
+ # config/initializers/secvault.rb
73
+ Secvault.setup_backward_compatibility_with_older_rails!
178
74
  ```
179
75
 
180
- ## Older Rails Integration
76
+ ## ERB Templating
181
77
 
182
- For Rails versions with existing secrets functionality (like Rails 7.1), use Secvault to test before upgrading:
78
+ Supports full ERB templating for environment variables:
183
79
 
184
- ```ruby
185
- # config/initializers/secvault.rb
186
- Secvault.setup_backward_compatibility_with_older_rails!
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(',') %>
187
87
  ```
188
88
 
189
- This replaces Rails.application.secrets with Secvault functionality. Your existing Rails 7.1 code works unchanged:
89
+ ## Development Tools
190
90
 
191
- ```ruby
192
- Rails.application.secrets.api_key # ✅ Works
193
- Rails.application.secrets.oauth_settings # ✅ Works
194
- Rails::Secrets.load # ✅ Load default config/secrets.yml
195
- Rails::Secrets.parse(['custom.yml'], env: Rails.env) # ✅ Parse custom files
196
- ```
91
+ Reload secrets in development:
197
92
 
198
- **Check if Secvault is active:**
199
93
  ```ruby
200
- if Secvault.active?
201
- puts "Using Secvault for secrets management"
202
- else
203
- puts "Using default Rails secrets functionality"
204
- end
94
+ # Available after setup_multi_file!
95
+ reload_secrets!
96
+ Rails.application.reload_secrets!
205
97
  ```
206
98
 
207
- ## Security Best Practices
208
-
209
- - **Never commit production secrets** to version control
210
- - **Use environment variables** in production with ERB: `<%= ENV['SECRET'] %>`
211
- - **Use ENV.fetch()** with fallbacks: `<%= ENV.fetch('SECRET', 'default') %>`
212
-
213
99
  ## License
214
100
 
215
- MIT License - see [LICENSE](https://opensource.org/licenses/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
@@ -46,15 +46,9 @@ module Secvault
46
46
  end
47
47
 
48
48
  # Monkey patch to restore Rails::Secrets interface for backwards compatibility
49
- # Only for Rails 7.2+ where Rails::Secrets was removed
50
- if defined?(Rails) && Rails.respond_to?(:version)
51
- rails_version = Rails.version
52
- major, minor = rails_version.split('.').map(&:to_i)
53
-
54
- # Only alias for Rails 7.2+ to avoid conflicts with native Rails::Secrets in 7.1
55
- if major > 7 || (major == 7 && minor >= 2)
56
- module Rails
57
- Secrets = Secvault::RailsSecrets
58
- end
49
+ # Works consistently across all Rails versions with warning suppression
50
+ if defined?(Rails)
51
+ module Rails
52
+ Secrets = Secvault::RailsSecrets
59
53
  end
60
54
  end
@@ -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['RAILS_ENV'] || 'development'
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
@@ -11,85 +11,67 @@ module Secvault
11
11
  class Secrets
12
12
  class << self
13
13
  def setup(app)
14
- # Only auto-setup for Rails 7.2+ where secrets functionality was removed
15
- return unless rails_7_2_or_later?
16
-
14
+ # Auto-setup for all Rails versions with consistent behavior
17
15
  secrets_path = app.root.join("config/secrets.yml")
18
16
 
19
- if secrets_path.exist?
20
- # Use a more reliable approach that works in all environments
21
- app.config.before_configuration do
22
- current_env = ENV['RAILS_ENV'] || Rails.env || 'development'
23
- setup_secrets_immediately(app, secrets_path, current_env)
24
- end
25
-
26
- # Also try during to_prepare as a fallback
27
- app.config.to_prepare do
28
- current_env = Rails.env
29
- unless Rails.application.respond_to?(:secrets) && !Rails.application.secrets.empty?
30
- setup_secrets_immediately(app, secrets_path, current_env)
31
- end
32
- end
17
+ return unless secrets_path.exist?
18
+
19
+ # Use a reliable approach that works in all environments
20
+ app.config.before_configuration do
21
+ current_env = ENV["RAILS_ENV"] || Rails.env || "development"
22
+ setup_secrets_immediately(app, secrets_path, current_env)
33
23
  end
34
- end
35
-
36
- # Manual setup method for Rails 7.1 (opt-in)
37
- def setup_for_rails_71!(app)
38
- secrets_path = app.root.join("config/secrets.yml")
39
24
 
40
- if secrets_path.exist?
41
- app.config.before_configuration do
42
- current_env = ENV['RAILS_ENV'] || Rails.env || 'development'
25
+ # Also try during to_prepare as a fallback
26
+ app.config.to_prepare do
27
+ current_env = Rails.env
28
+ unless Rails.application.respond_to?(:secrets) && !Rails.application.secrets.empty?
43
29
  setup_secrets_immediately(app, secrets_path, current_env)
44
30
  end
45
31
  end
46
32
  end
47
33
 
48
- def setup_secrets_immediately(app, secrets_path, env)
34
+ def setup_secrets_immediately(_app, secrets_path, env)
49
35
  # Set up secrets if they exist
50
36
  secrets = read_secrets(secrets_path, env)
51
- if secrets
52
- # Rails 8.0+ compatibility: Add secrets accessor that initializes on first access
53
- unless Rails.application.respond_to?(:secrets)
54
- Rails.application.define_singleton_method(:secrets) do
55
- @secrets ||= begin
56
- current_secrets = ActiveSupport::OrderedOptions.new
57
- # Re-read secrets to ensure we have the right environment
58
- env_secrets = Secvault::Secrets.read_secrets(secrets_path, Rails.env)
59
- current_secrets.merge!(env_secrets) if env_secrets
60
- current_secrets
61
- end
37
+ return unless secrets
38
+
39
+ # Rails 8.0+ compatibility: Add secrets accessor that initializes on first access
40
+ unless Rails.application.respond_to?(:secrets)
41
+ Rails.application.define_singleton_method(:secrets) do
42
+ @secrets ||= begin
43
+ current_secrets = ActiveSupport::OrderedOptions.new
44
+ # Re-read secrets to ensure we have the right environment
45
+ env_secrets = Secvault::Secrets.read_secrets(secrets_path, Rails.env)
46
+ current_secrets.merge!(env_secrets) if env_secrets
47
+ current_secrets
62
48
  end
63
49
  end
64
-
65
- # If secrets accessor already exists, merge the secrets
66
- if Rails.application.respond_to?(:secrets) && Rails.application.secrets.respond_to?(:merge!)
67
- Rails.application.secrets.merge!(secrets)
68
- end
69
50
  end
51
+
52
+ # If secrets accessor already exists, merge the secrets
53
+ return unless Rails.application.respond_to?(:secrets) && Rails.application.secrets.respond_to?(:merge!)
54
+
55
+ Rails.application.secrets.merge!(secrets)
70
56
  end
71
57
 
72
58
  # Classic Rails::Secrets.parse implementation
73
59
  # Parses plain YAML secrets files and merges shared + environment-specific sections
74
60
  def parse(paths, env:)
75
- paths.each_with_object(Hash.new) do |path, all_secrets|
61
+ paths.each_with_object({}) do |path, all_secrets|
76
62
  # Handle string paths by converting to Pathname
77
63
  path = Pathname.new(path) unless path.respond_to?(:exist?)
78
64
  next unless path.exist?
79
-
65
+
80
66
  # Read and process the plain YAML file content
81
67
  source = path.read
82
-
68
+
83
69
  # Process ERB and parse YAML
84
70
  erb_result = ERB.new(source).result
85
- secrets = if YAML.respond_to?(:unsafe_load)
86
- YAML.unsafe_load(erb_result)
87
- else
88
- YAML.load(erb_result)
89
- end
90
-
71
+ secrets = YAML.safe_load(erb_result, aliases: true, permitted_classes: [])
72
+
91
73
  secrets ||= {}
92
-
74
+
93
75
  # Merge shared secrets first, then environment-specific (using deep merge)
94
76
  all_secrets.deep_merge!(secrets["shared"].deep_symbolize_keys) if secrets["shared"]
95
77
  all_secrets.deep_merge!(secrets[env].deep_symbolize_keys) if secrets[env]
@@ -100,21 +82,13 @@ module Secvault
100
82
  if secrets_path.exist?
101
83
  # Handle plain YAML secrets.yml only
102
84
  all_secrets = YAML.safe_load(ERB.new(secrets_path.read).result, aliases: true)
103
-
85
+
104
86
  env_secrets = all_secrets[env.to_s]
105
87
  return env_secrets.deep_symbolize_keys if env_secrets
106
88
  end
107
89
 
108
90
  {}
109
91
  end
110
-
111
- private
112
-
113
- def rails_7_2_or_later?
114
- rails_version = Rails.version
115
- major, minor = rails_version.split('.').map(&:to_i)
116
- major > 7 || (major == 7 && minor >= 2)
117
- end
118
92
  end
119
93
  end
120
94
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Secvault
4
- VERSION = "2.6.0"
4
+ VERSION = "2.7.1"
5
5
  end