localvault 1.13.0 → 1.14.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: aa374ffc862a0591ab2936ea836ea4f04e96d3d86acd3591fb1b57ed91ea1253
4
- data.tar.gz: f8ac6c6bd7e329d395801741f1c9e61f121339d5f05d013623faa6e2f7d255e0
3
+ metadata.gz: 2f77c11508eef73c90760e5a927201f6735a16d836e6890161e89a497e28d917
4
+ data.tar.gz: 15400be786d2ba53112d1b6b48a1de5bd0518903e5578c859677ec68320f2b8b
5
5
  SHA512:
6
- metadata.gz: 3cfb2aaccbfb4c0fc8882d6115dc3ca7ab8802f060601f3083ec9d1c36f76b1688ea2649eea70edaaf18d463f87e35605311a7a5492094f1e22d70326771b1ae
7
- data.tar.gz: 874a4be548a5f7498d9fa520be60485f762df2f4ebe2e4612816b16617bf072cb1c735f73375ace33638c73d309d0a86c294095ea1c777ccfc57a3b9666b1eca
6
+ metadata.gz: c8fcd7235f9e330cdf7cf2c17528849f7b64b4388e865351ab53c1758b3390d32dd638c2c21e7ba24ff18e7d9c63e7c5e0bd2c66efa87792380d9d8ef12b61b7
7
+ data.tar.gz: c8491e0b0fcb2e588080496254b4fa04195859ad81d7ce66c38d4e13116d20374fa9f96668cc18cf8dc2a0830affb58ea59df84ed6eb1531c817942f364c54e3
@@ -0,0 +1,58 @@
1
+ module LocalVault
2
+ class CLI
3
+ # Finds a Rails app's credentials keys so they can be stored in the vault
4
+ # and injected as RAILS_MASTER_KEY instead of living in a file.
5
+ #
6
+ # Reads only. Removing the key files is the operator's call — this reports
7
+ # what is still on disk and leaves it alone.
8
+ class RailsImport
9
+ Key = Struct.new(:environment, :path, :vault_key, :value, keyword_init: true)
10
+
11
+ MASTER_KEY_PATH = "config/master.key".freeze
12
+ CREDENTIALS_PATH = "config/credentials.yml.enc".freeze
13
+
14
+ def initialize(root = Dir.pwd)
15
+ @root = root
16
+ end
17
+
18
+ def rails_app?
19
+ File.exist?(File.join(@root, CREDENTIALS_PATH)) ||
20
+ File.exist?(File.join(@root, MASTER_KEY_PATH)) ||
21
+ Dir.exist?(File.join(@root, "config/credentials"))
22
+ end
23
+
24
+ # Every key file this app has: the default master key plus one per
25
+ # environment (config/credentials/production.key).
26
+ def keys
27
+ [master_key, *environment_keys].compact
28
+ end
29
+
30
+ private
31
+
32
+ def master_key
33
+ path = File.join(@root, MASTER_KEY_PATH)
34
+ return nil unless File.exist?(path)
35
+
36
+ value = File.read(path).strip
37
+ return nil if value.empty?
38
+
39
+ Key.new(environment: nil, path: MASTER_KEY_PATH, vault_key: "rails.master_key", value: value)
40
+ end
41
+
42
+ def environment_keys
43
+ Dir.glob(File.join(@root, "config/credentials/*.key")).sort.filter_map do |path|
44
+ environment = File.basename(path, ".key")
45
+ value = File.read(path).strip
46
+ next if value.empty?
47
+
48
+ Key.new(
49
+ environment: environment,
50
+ path: "config/credentials/#{environment}.key",
51
+ vault_key: "rails.#{environment}_key",
52
+ value: value
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -116,7 +116,7 @@ module LocalVault
116
116
  # disappearing from help.
117
117
  HELP_SECTIONS = [
118
118
  ["GETTING STARTED", %w[login config init demo]],
119
- ["SECRETS", %w[set get show reveal groups list delete import env exec]],
119
+ ["SECRETS", %w[set get show reveal groups list delete import env exec rails]],
120
120
  ["VAULT MANAGEMENT", %w[vaults switch rekey unlock lock reset rename copy]],
121
121
  ["SYNC (requires localvault login)", %w[sync]],
122
122
  ["TEAM SHARING (requires localvault login)", %w[dashboard verify add remove team]],
@@ -807,6 +807,7 @@ module LocalVault
807
807
  require_relative "cli/sync"
808
808
  require_relative "cli/guard"
809
809
  require_relative "cli/identity_cmd"
810
+ require_relative "cli/rails_import"
810
811
 
811
812
  # Thor 1.5 injects a `tree` command into every class. Inside a namespace it
812
813
  # lists under a name that isn't even callable (`identity_command tree`), and
@@ -1649,6 +1650,67 @@ module LocalVault
1649
1650
  end
1650
1651
  end
1651
1652
 
1653
+ desc "rails", "Import a Rails app's credentials keys into the vault"
1654
+ long_desc <<~DESC
1655
+ Store this Rails app's credentials keys in the vault so you can run it
1656
+ with no key file on disk. Rails reads ENV["RAILS_MASTER_KEY"] whenever
1657
+ config/master.key is absent, so injecting that one variable is enough.
1658
+
1659
+ \x05 localvault rails # import config/master.key (and per-env keys)
1660
+ \x05 localvault rails --check # show what would be imported
1661
+
1662
+ Then run your app with the key injected, never written anywhere:
1663
+
1664
+ \x05 localvault exec --profile rails -- bin/rails server
1665
+ \x05 localvault exec --profile rails -- bin/rails credentials:edit
1666
+
1667
+ For a per-environment key (config/credentials/production.key), map it —
1668
+ Rails reads no variable other than RAILS_MASTER_KEY:
1669
+
1670
+ \x05 localvault exec --map rails.production_key=RAILS_MASTER_KEY -- bin/rails console
1671
+
1672
+ Key files are left exactly as they are. Deleting them is your call.
1673
+ DESC
1674
+ method_option :check, type: :boolean, default: false, desc: "List what would be imported without writing"
1675
+ def rails
1676
+ importer = RailsImport.new
1677
+ unless importer.rails_app?
1678
+ abort_with "No Rails app here — expected config/credentials.yml.enc or config/master.key in #{Dir.pwd}"
1679
+ return CommandStatus.error
1680
+ end
1681
+
1682
+ keys = importer.keys
1683
+ if keys.empty?
1684
+ $stdout.puts "No credentials key files found (config/master.key, config/credentials/*.key)."
1685
+ $stdout.puts "Nothing to import — the app may already run from RAILS_MASTER_KEY."
1686
+ return CommandStatus.ok
1687
+ end
1688
+
1689
+ if options[:check]
1690
+ $stdout.puts "Would import into vault '#{options[:vault] || Config.default_vault}':"
1691
+ keys.each { |key| $stdout.puts " #{key.path} -> #{key.vault_key}" }
1692
+ return CommandStatus.ok
1693
+ end
1694
+
1695
+ vault = open_vault!
1696
+ keys.each do |key|
1697
+ vault.set(key.vault_key, key.value)
1698
+ $stdout.puts "Imported #{key.path} -> #{key.vault_key}"
1699
+ end
1700
+
1701
+ $stdout.puts
1702
+ $stdout.puts "Run without a key file on disk:"
1703
+ $stdout.puts " localvault exec --profile rails -- bin/rails server"
1704
+ keys.select(&:environment).each do |key|
1705
+ $stdout.puts " localvault exec --map #{key.vault_key}=RAILS_MASTER_KEY -- bin/rails console # #{key.environment}"
1706
+ end
1707
+
1708
+ $stdout.puts
1709
+ $stdout.puts "Note: the key files are still on disk (#{keys.map(&:path).join(", ")})."
1710
+ $stdout.puts "They keep working until you remove them — that's your call, not ours."
1711
+ CommandStatus.ok
1712
+ end
1713
+
1652
1714
  desc "upgrade", "Upgrade localvault using whichever method installed it"
1653
1715
  long_desc <<~DESC
1654
1716
  Detect how this copy of localvault was installed and run the matching
@@ -17,9 +17,24 @@ module LocalVault
17
17
  "AWS_IAM.secret_access_key" => "AWS_SECRET_ACCESS_KEY",
18
18
  "AWS_IAM.session_token" => "AWS_SESSION_TOKEN"
19
19
  }
20
+ },
21
+ # Rails reads its credentials key from ENV["RAILS_MASTER_KEY"] when
22
+ # config/master.key is absent (railties: encrypted(..., env_key:
23
+ # "RAILS_MASTER_KEY")), so injecting that one variable is enough to run a
24
+ # Rails app with no key file on disk.
25
+ "rails" => {
26
+ only: ["rails.*"],
27
+ map: { "rails.master_key" => "RAILS_MASTER_KEY" }
20
28
  }
21
29
  }.freeze
22
30
 
31
+ # Per-environment credentials (config/credentials/production.key) still have
32
+ # to arrive as RAILS_MASTER_KEY — Rails reads no other variable. This builds
33
+ # the mapping for one environment.
34
+ def self.rails_environment_mapping(environment)
35
+ { "rails.#{environment}_key" => "RAILS_MASTER_KEY" }
36
+ end
37
+
23
38
  def self.entries(secrets, project: nil, only: nil, except: nil, map: nil, profile: nil, on_skip: nil)
24
39
  profile_config = profile_config(profile)
25
40
  selectors = parse_selectors(only) || profile_config[:only]
@@ -4,7 +4,7 @@ module LocalVault
4
4
 
5
5
  SEGMENT_PATTERN = /\A[A-Za-z_][A-Za-z0-9_]*\z/
6
6
  VAULT_PATTERN = /\A[A-Za-z0-9][A-Za-z0-9_-]{0,63}\z/
7
- PROFILES = %w[aws].freeze
7
+ PROFILES = %w[aws rails].freeze
8
8
 
9
9
  module_function
10
10
 
@@ -1,3 +1,3 @@
1
1
  module LocalVault
2
- VERSION = "1.13.0"
2
+ VERSION = "1.14.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: localvault
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.0
4
+ version: 1.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nauman Tariq
@@ -115,6 +115,7 @@ files:
115
115
  - lib/localvault/cli/help_shell.rb
116
116
  - lib/localvault/cli/identity_cmd.rb
117
117
  - lib/localvault/cli/keys.rb
118
+ - lib/localvault/cli/rails_import.rb
118
119
  - lib/localvault/cli/sync.rb
119
120
  - lib/localvault/cli/team.rb
120
121
  - lib/localvault/cli/team_helpers.rb