secvault 1.0.0 → 1.0.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: 67a57fa1ebe481bab8c9f5f4d9f6d0af27250e4d2c145641c125cd61f88018d0
4
- data.tar.gz: 8613ed6d87bf067ad69bde7d80d8623a30b6dfb36280ddfe431b236198e63e2c
3
+ metadata.gz: 05e6d8b6e701bdc5bd47c0ab65ea6af571f6476a056fea095bb9ab82085071fe
4
+ data.tar.gz: 535caed7383f6c29c605ec8a68df0a0e0210a2d713ba898d03509a9ae68bca95
5
5
  SHA512:
6
- metadata.gz: 3013b492cec7b62296815ea239dc398de08806532ca995de383324a62fd9689e44ae62c744365dbcd29e1158b94b204f2b1522986c5e2d9b1e0d024cab3c871d
7
- data.tar.gz: 2c74fa262a228253393a7b5818cb6d59554b67fb6ecbfac37d018c271fea9d6ca38d6c8b2cd84d253e16c42d4b28144c6059a1ee5e297d950db2954de6a5285b
6
+ metadata.gz: bb4b1cf849d5a319f2b2911c9764cec1383016a409dfcd05258d36e5d1c9d5426e107356962d1a7b2c07c896e7586ae034ec39a70966621894de33c28cd7aa25
7
+ data.tar.gz: d71b470eb2682412103c10224d27054ab4c768a8608065b8cd0d86cf3cda2a5c28a73f51097cdb4ff08e6e088e38c5f159e7c6df25893b114fd10f4322319bc3
@@ -9,9 +9,10 @@ module Secvault
9
9
  class SecretsGenerator < Rails::Generators::Base
10
10
  source_root File.expand_path("templates", __dir__)
11
11
 
12
- desc "Creates a secrets.yml file for encrypted secrets management"
12
+ desc "Creates a secrets.yml file for secrets management"
13
13
 
14
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)"
15
16
 
16
17
  def create_secrets_file
17
18
  secrets_path = Rails.root.join("config/secrets.yml")
@@ -22,27 +23,32 @@ module Secvault
22
23
  return
23
24
  end
24
25
 
25
- # Generate encryption key
26
- unless key_path.exist?
27
- key = ActiveSupport::EncryptedFile.generate_key
28
- File.write(key_path, key)
29
- say "Generated encryption key in config/secrets.yml.key", :green
30
- end
26
+ default_content = generate_default_secrets
31
27
 
32
- # Create encrypted secrets file with template
33
- encrypted_file = ActiveSupport::EncryptedFile.new(
34
- content_path: secrets_path,
35
- key_path: key_path,
36
- env_key: "RAILS_SECRETS_KEY",
37
- raise_if_missing_key: true
38
- )
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
39
35
 
40
- # Write default content
41
- default_content = generate_default_secrets
42
- encrypted_file.write(default_content)
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
+ )
43
42
 
44
- say "Created encrypted secrets.yml file", :green
45
- say "Add config/secrets.yml.key to your .gitignore file", :yellow
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
46
52
  end
47
53
 
48
54
  def add_to_gitignore
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Secvault
4
+ # Rails::Secrets compatibility module
5
+ # Provides the classic Rails::Secrets interface for backwards compatibility
6
+ # This replicates the Rails < 7.2 Rails::Secrets module functionality
7
+ module RailsSecrets
8
+ extend self
9
+
10
+ # Classic Rails::Secrets.parse method
11
+ #
12
+ # Parses secrets files with support for:
13
+ # - ERB templating
14
+ # - Shared sections that apply to all environments
15
+ # - Environment-specific sections
16
+ # - Deep symbolized keys
17
+ #
18
+ # Example usage:
19
+ # Rails::Secrets.parse([Pathname.new('config/secrets.yml')], env: 'development')
20
+ #
21
+ # Example secrets.yml structure:
22
+ # shared:
23
+ # common_key: shared_value
24
+ #
25
+ # development:
26
+ # secret_key_base: dev_secret
27
+ # api_key: dev_api_key
28
+ #
29
+ # production:
30
+ # secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
31
+ # api_key: <%= ENV["API_KEY"] %>
32
+ #
33
+ def parse(paths, env:)
34
+ Secvault::Secrets.parse(paths, env: env.to_s)
35
+ end
36
+
37
+ # Convenience method to parse the default secrets file
38
+ def parse_default(env: Rails.env)
39
+ secrets_path = Rails.root.join("config/secrets.yml")
40
+ parse([secrets_path], env: env)
41
+ end
42
+
43
+ # Read and parse secrets for the current Rails environment
44
+ def read(env: Rails.env)
45
+ parse_default(env: env)
46
+ end
47
+ end
48
+ end
49
+
50
+ # Monkey patch to restore Rails::Secrets interface for backwards compatibility
51
+ module Rails
52
+ Secrets = Secvault::RailsSecrets
53
+ end
@@ -6,9 +6,32 @@ module Secvault
6
6
  class Railtie < Rails::Railtie
7
7
  railtie_name :secvault
8
8
 
9
- initializer "secvault.initialize" do |app|
9
+ initializer "secvault.initialize", before: :load_environment_hook do |app|
10
10
  Secvault::Secrets.setup(app)
11
11
  end
12
+
13
+ # Ensure initialization happens early in all environments
14
+ config.before_configuration do |app|
15
+ secrets_path = app.root.join("config/secrets.yml")
16
+ key_path = app.root.join("config/secrets.yml.key")
17
+
18
+ if secrets_path.exist? && !Rails.application.respond_to?(:secrets)
19
+ # Early initialization for test environment compatibility
20
+ current_env = ENV['RAILS_ENV'] || 'development'
21
+ secrets = Secvault::Secrets.read_secrets(secrets_path, key_path, current_env)
22
+
23
+ if secrets
24
+ Rails.application.define_singleton_method(:secrets) do
25
+ @secrets ||= begin
26
+ current_secrets = ActiveSupport::OrderedOptions.new
27
+ env_secrets = Secvault::Secrets.read_secrets(secrets_path, key_path, Rails.env)
28
+ current_secrets.merge!(env_secrets) if env_secrets
29
+ current_secrets
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
12
35
 
13
36
  generators do
14
37
  require "secvault/generators/secrets_generator"
@@ -3,6 +3,7 @@
3
3
  require "active_support/encrypted_file"
4
4
  require "active_support/core_ext/hash/keys"
5
5
  require "active_support/core_ext/object/blank"
6
+ require "active_support/ordered_options"
6
7
  require "pathname"
7
8
  require "erb"
8
9
  require "yaml"
@@ -15,27 +16,78 @@ module Secvault
15
16
  key_path = app.root.join("config/secrets.yml.key")
16
17
 
17
18
  if secrets_path.exist?
19
+ # Use a more reliable approach that works in all environments
18
20
  app.config.before_configuration do
19
- # Set up secrets if they exist
20
- secrets = read_secrets(secrets_path, key_path, Rails.env)
21
- Rails.application.secrets.merge!(secrets) if secrets
21
+ current_env = ENV['RAILS_ENV'] || Rails.env || 'development'
22
+ setup_secrets_immediately(app, secrets_path, key_path, current_env)
23
+ end
24
+
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?
29
+ setup_secrets_immediately(app, secrets_path, key_path, current_env)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def setup_secrets_immediately(app, secrets_path, key_path, env)
36
+ # Set up secrets if they exist
37
+ secrets = read_secrets(secrets_path, key_path, env)
38
+ if secrets
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, key_path, Rails.env)
46
+ current_secrets.merge!(env_secrets) if env_secrets
47
+ current_secrets
48
+ end
49
+ end
50
+ end
51
+
52
+ # If secrets accessor already exists, merge the secrets
53
+ if Rails.application.respond_to?(:secrets) && Rails.application.secrets.respond_to?(:merge!)
54
+ Rails.application.secrets.merge!(secrets)
22
55
  end
23
56
  end
24
57
  end
25
58
 
59
+ # Classic Rails::Secrets.parse implementation
60
+ # Parses secrets files and merges shared + environment-specific sections
26
61
  def parse(paths, env:)
27
- configs = paths.collect do |path|
28
- if path.exist?
29
- content = encrypted?(path) ? decrypt(path) : path.read
30
- YAML.safe_load(ERB.new(content).result, aliases: true) || {}
62
+ paths.each_with_object(Hash.new) do |path, all_secrets|
63
+ next unless path.exist?
64
+
65
+ # Read and process the file content (handle both encrypted and plain)
66
+ source = if encrypted?(path)
67
+ decrypt(path)
68
+ else
69
+ preprocess(path)
70
+ end
71
+
72
+ # Process ERB and parse YAML
73
+ erb_result = ERB.new(source).result
74
+ secrets = if YAML.respond_to?(:unsafe_load)
75
+ YAML.unsafe_load(erb_result)
31
76
  else
32
- {}
77
+ YAML.load(erb_result)
33
78
  end
79
+
80
+ secrets ||= {}
81
+
82
+ # Merge shared secrets first, then environment-specific
83
+ all_secrets.merge!(secrets["shared"].deep_symbolize_keys) if secrets["shared"]
84
+ all_secrets.merge!(secrets[env].deep_symbolize_keys) if secrets[env]
34
85
  end
35
-
36
- configs.reverse.reduce do |config, overrides|
37
- config.deep_merge(overrides)
38
- end[env] || {}
86
+ end
87
+
88
+ # Helper method to preprocess plain YAML files (for ERB)
89
+ def preprocess(path)
90
+ path.read
39
91
  end
40
92
 
41
93
  def read_secrets(secrets_path, key_path, env)
@@ -4,31 +4,24 @@ require "active_support/encrypted_file"
4
4
  require "securerandom"
5
5
 
6
6
  namespace :secvault do
7
- desc "Setup encrypted secrets.yml file"
7
+ desc "Setup secrets.yml file (plain YAML by default)"
8
8
  task setup: :environment do
9
9
  secrets_path = Rails.root.join("config/secrets.yml")
10
10
  key_path = Rails.root.join("config/secrets.yml.key")
11
+ encrypted = ENV["ENCRYPTED"] == "true"
11
12
 
12
13
  if secrets_path.exist?
13
14
  puts "Secrets file already exists at #{secrets_path}"
14
15
  else
15
- # Generate key if it doesn't exist
16
- unless key_path.exist?
17
- key = ActiveSupport::EncryptedFile.generate_key
18
- File.write(key_path, key)
19
- puts "Generated encryption key in #{key_path}"
20
- end
21
-
22
- # Create encrypted file with default content
23
- encrypted_file = ActiveSupport::EncryptedFile.new(
24
- content_path: secrets_path,
25
- key_path: key_path,
26
- env_key: "RAILS_SECRETS_KEY",
27
- raise_if_missing_key: true
28
- )
29
-
30
16
  default_content = <<~YAML
31
17
  # Be sure to restart your server when you modify this file.
18
+ #
19
+ # Your secret key is used for verifying the integrity of signed cookies.
20
+ # If you change this key, all old signed cookies will become invalid!
21
+ #
22
+ # Make sure the secret is at least 30 characters and all random,
23
+ # no regular words or you'll be exposed to dictionary attacks.
24
+ # You can use `rails secret` to generate a secure secret key.
32
25
 
33
26
  development:
34
27
  secret_key_base: #{SecureRandom.hex(64)}
@@ -36,17 +29,39 @@ namespace :secvault do
36
29
  test:
37
30
  secret_key_base: #{SecureRandom.hex(64)}
38
31
 
32
+ # Do not keep production secrets in the repository,
33
+ # instead read values from the environment.
39
34
  production:
40
35
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
41
36
  YAML
42
37
 
43
- encrypted_file.write(default_content)
44
- puts "Created encrypted secrets.yml file"
45
- puts "Add #{key_path} to your .gitignore file"
38
+ if encrypted
39
+ # Create encrypted file
40
+ unless key_path.exist?
41
+ key = ActiveSupport::EncryptedFile.generate_key
42
+ File.write(key_path, key)
43
+ puts "Generated encryption key in #{key_path}"
44
+ end
45
+
46
+ encrypted_file = ActiveSupport::EncryptedFile.new(
47
+ content_path: secrets_path,
48
+ key_path: key_path,
49
+ env_key: "RAILS_SECRETS_KEY",
50
+ raise_if_missing_key: true
51
+ )
52
+ encrypted_file.write(default_content)
53
+ puts "Created encrypted secrets.yml file"
54
+ puts "Add #{key_path} to your .gitignore file"
55
+ else
56
+ # Create plain YAML file
57
+ File.write(secrets_path, default_content)
58
+ puts "Created plain secrets.yml file"
59
+ puts "Remember to add #{secrets_path} to your .gitignore if it contains sensitive data"
60
+ end
46
61
  end
47
62
  end
48
63
 
49
- desc "Edit encrypted secrets.yml file"
64
+ desc "Edit secrets.yml file"
50
65
  task edit: :environment do
51
66
  secrets_path = Rails.root.join("config/secrets.yml")
52
67
  key_path = Rails.root.join("config/secrets.yml.key")
@@ -56,18 +71,28 @@ namespace :secvault do
56
71
  exit 1
57
72
  end
58
73
 
59
- encrypted_file = ActiveSupport::EncryptedFile.new(
60
- content_path: secrets_path,
61
- key_path: key_path,
62
- env_key: "RAILS_SECRETS_KEY",
63
- raise_if_missing_key: true
64
- )
74
+ # Check if file is encrypted
75
+ is_encrypted = Secvault::Secrets.encrypted?(secrets_path)
65
76
 
66
- encrypted_file.change do |tmp_path|
67
- system("#{ENV["EDITOR"] || "vi"} #{tmp_path}")
68
- end
77
+ if is_encrypted && key_path.exist?
78
+ # Handle encrypted file
79
+ encrypted_file = ActiveSupport::EncryptedFile.new(
80
+ content_path: secrets_path,
81
+ key_path: key_path,
82
+ env_key: "RAILS_SECRETS_KEY",
83
+ raise_if_missing_key: true
84
+ )
85
+
86
+ encrypted_file.change do |tmp_path|
87
+ system("#{ENV["EDITOR"] || "vi"} #{tmp_path}")
88
+ end
69
89
 
70
- puts "Updated #{secrets_path}"
90
+ puts "Updated encrypted #{secrets_path}"
91
+ else
92
+ # Handle plain YAML file
93
+ system("#{ENV["EDITOR"] || "vi"} #{secrets_path}")
94
+ puts "Updated plain #{secrets_path}"
95
+ end
71
96
  rescue ActiveSupport::EncryptedFile::MissingKeyError
72
97
  puts "Missing encryption key to decrypt secrets.yml."
73
98
  puts "Ask your team for your secrets key and put it in #{key_path}"
@@ -75,7 +100,7 @@ namespace :secvault do
75
100
  puts "Invalid encryption key for secrets.yml."
76
101
  end
77
102
 
78
- desc "Show decrypted secrets.yml content"
103
+ desc "Show secrets.yml content"
79
104
  task show: :environment do
80
105
  secrets_path = Rails.root.join("config/secrets.yml")
81
106
  key_path = Rails.root.join("config/secrets.yml.key")
@@ -85,14 +110,22 @@ namespace :secvault do
85
110
  exit 1
86
111
  end
87
112
 
88
- encrypted_file = ActiveSupport::EncryptedFile.new(
89
- content_path: secrets_path,
90
- key_path: key_path,
91
- env_key: "RAILS_SECRETS_KEY",
92
- raise_if_missing_key: true
93
- )
113
+ # Check if file is encrypted
114
+ is_encrypted = Secvault::Secrets.encrypted?(secrets_path)
94
115
 
95
- puts encrypted_file.read
116
+ if is_encrypted && key_path.exist?
117
+ # Handle encrypted file
118
+ encrypted_file = ActiveSupport::EncryptedFile.new(
119
+ content_path: secrets_path,
120
+ key_path: key_path,
121
+ env_key: "RAILS_SECRETS_KEY",
122
+ raise_if_missing_key: true
123
+ )
124
+ puts encrypted_file.read
125
+ else
126
+ # Handle plain YAML file
127
+ puts File.read(secrets_path)
128
+ end
96
129
  rescue ActiveSupport::EncryptedFile::MissingKeyError
97
130
  puts "Missing encryption key to decrypt secrets.yml."
98
131
  puts "Ask your team for your secrets key and put it in #{key_path}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Secvault
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.1"
5
5
  end
data/lib/secvault.rb CHANGED
@@ -20,9 +20,10 @@ module Secvault
20
20
  extend self
21
21
 
22
22
  def install!
23
- return if Rails.env.test? || defined?(Rails::Railtie).nil?
23
+ return if defined?(Rails::Railtie).nil?
24
24
 
25
25
  require "secvault/railtie"
26
+ require "secvault/rails_secrets"
26
27
  end
27
28
  end
28
29
 
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: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Unnikrishnan KP
@@ -40,7 +40,7 @@ dependencies:
40
40
  version: '2.6'
41
41
  description: Secvault restores the classic Rails secrets.yml functionality that was
42
42
  removed in Rails 7.2, allowing you to manage encrypted secrets using the familiar
43
- YAML-based approach.
43
+ YAML-based approach. Compatible with Rails 8.0+.
44
44
  email:
45
45
  - unnikrishnan.kp@bigbinary.com
46
46
  executables: []
@@ -56,6 +56,7 @@ files:
56
56
  - Rakefile
57
57
  - lib/secvault.rb
58
58
  - lib/secvault/generators/secrets_generator.rb
59
+ - lib/secvault/rails_secrets.rb
59
60
  - lib/secvault/railtie.rb
60
61
  - lib/secvault/secrets.rb
61
62
  - lib/secvault/secrets_helper.rb
@@ -88,5 +89,5 @@ requirements: []
88
89
  rubygems_version: 3.5.10
89
90
  signing_key:
90
91
  specification_version: 4
91
- summary: Rails secrets.yml functionality for Rails 7.2+
92
+ summary: Rails secrets.yml functionality for Rails 7.2+ and Rails 8.0+
92
93
  test_files: []