rails_outofband_keys 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c028a4ebdbf7196c3cdaa3279dfda0dd8d5d75d25a9b662c847847d650087b1d
4
+ data.tar.gz: 6d393b4056c6e6bfd9b63f7bdbb7dbba10a29544ca833a7e09109d71498f59c1
5
+ SHA512:
6
+ metadata.gz: 8d0f2b467ced1b33e8e139843413f4e1f0c21e524a73eb41985dba3c75091a9d8818bd87976b21cab592ca4f5d325e426991720ef7d3cd62b36347ab96597202
7
+ data.tar.gz: 642e1b15c124d6dc4fee45991c69fee2b36ef486f6428a61b7844b371350c1b9de55b07f70d8b70e4dfd11a56ae0e5ef4ca30ebee770f65688cdaa7cab10f485
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # rails_outofband_keys
2
+
3
+ `rails_outofband_keys` is a Rails plugin that changes **how Rails finds your credentials key files** (e.g., `production.key` or `master.key`). It allows you to keep these sensitive keys **outside of your project directory and git tree** (e.g., in `~/.config/`).
4
+
5
+ It does **not** replace Rails credentials, change where `credentials.yml.enc` lives, or alter how encryption works. It only dynamically configures `config.credentials.key_path` during the boot process.
6
+
7
+ ## Resolution Order
8
+
9
+ 1. If `RAILS_MASTER_KEY` is set in the environment, Rails uses it (this gem does nothing).
10
+ 2. If `RAILS_CREDENTIALS_KEY_DIR` is set, it is used as the base directory for the app.
11
+ 3. If `RAILS_OUTOFBAND_BASE_DIR` is set, it is used as the global base directory.
12
+ 4. Otherwise, the gem fallbacks to OS defaults:
13
+ - **Linux/macOS**: XDG config directory (`~/.config` fallback)
14
+ - **Windows**: `%AppData%`
15
+
16
+ The final path is constructed as:
17
+ `base_directory / root_subdir / credentials_subdir / <environment>.key`
18
+ `base_directory / root_subdir / credentials_subdir / master.key`
19
+
20
+ ## Configuration
21
+
22
+ Because Rails initializes credentials very early (especially for CLI tools like `credentials:edit`), this gem is configured via a YAML file located at `config/rails_outofband_keys.yml`.
23
+
24
+ ### Example config/rails_outofband_keys.yml
25
+
26
+ ```yaml
27
+ # The sub-folder for your application or organization
28
+ root_subdir: "my_org/my_app"
29
+
30
+ # The subdirectory where keys are stored (defaults to "credentials")
31
+ # Set to null if keys live directly in the root_subdir
32
+ credentials_subdir: "credentials"
33
+ ```
34
+
35
+ ## Permissions
36
+
37
+ On Unix-like systems, key files **must** have secure permissions. They must be owner-readable and **must not** be accessible by group or others (e.g., mode `0600` or `0400`). The app will raise an `InsecureKeyPermissionsError` and refuse to boot if permissions are too open.
38
+
39
+ ## Installation
40
+
41
+ Add the gem to your Gemfile:
42
+
43
+ ```ruby
44
+ gem "rails_outofband_keys", git: "git@github.com:lholden/rails_outofband_keys.git", tag: "v0.1.1"
45
+ ```
46
+
47
+ ## License
48
+
49
+ MIT
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOutofbandKeys
4
+ # Configuration for RailsOutofbandKeys.
5
+ class Config
6
+ attr_accessor :root_subdir, :credentials_subdir, :key_dir_env
7
+
8
+ def initialize
9
+ @root_subdir = nil
10
+ @credentials_subdir = "credentials"
11
+ @key_dir_env = "RAILS_CREDENTIALS_KEY_DIR"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOutofbandKeys
4
+ class Error < StandardError; end
5
+
6
+ # Error raised when key file permissions are too open.
7
+ class InsecureKeyPermissionsError < Error
8
+ def initialize(path, mode)
9
+ super("Insecure permissions on #{path} (#{mode}); expected 0600 or 0400")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require "pathname"
5
+ require "xdg"
6
+
7
+ module RailsOutofbandKeys
8
+ # Resolves the path to the credentials key file.
9
+ class KeyResolver
10
+ def initialize(config:, rails_env:, app_name:)
11
+ @config = config
12
+ @rails_env = rails_env
13
+ @app_name = app_name
14
+ end
15
+
16
+ def resolve_key_path
17
+ return nil if ENV["RAILS_MASTER_KEY"] && !ENV["RAILS_MASTER_KEY"].empty?
18
+
19
+ key = find_key_in_resolved_dir
20
+ enforce_permissions!(key) if key
21
+ key&.to_s
22
+ end
23
+
24
+ private
25
+
26
+ def find_key_in_resolved_dir
27
+ dir = resolved_key_dir
28
+ env_key = dir.join("#{@rails_env}.key")
29
+ return env_key if env_key.file?
30
+
31
+ master_key = dir.join("master.key")
32
+ master_key.file? ? master_key : nil
33
+ end
34
+
35
+ def resolved_key_dir
36
+ base = resolve_base_dir
37
+ root = @config.root_subdir || @app_name
38
+
39
+ path_parts = [base, root]
40
+ path_parts << @config.credentials_subdir if @config.credentials_subdir
41
+
42
+ Pathname.new(File.join(*path_parts))
43
+ end
44
+
45
+ public
46
+
47
+ def resolve_base_dir
48
+ # Specific application override
49
+ override = ENV[@config.key_dir_env].to_s.strip
50
+ return override unless override.empty?
51
+
52
+ # Global base override
53
+ global_base = ENV["RAILS_OUTOFBAND_BASE_DIR"].to_s.strip
54
+ return global_base unless global_base.empty?
55
+
56
+ return ENV.fetch("APPDATA") if Gem.win_platform?
57
+
58
+ XDG.new.config_home.to_s
59
+ end
60
+
61
+ def enforce_permissions!(path)
62
+ return if Gem.win_platform?
63
+
64
+ mode = File.stat(path).mode & 0o777
65
+
66
+ owner_only = mode.nobits?(0o077)
67
+ readable = mode.anybits?(0o400)
68
+
69
+ return if owner_only && readable
70
+
71
+ raise InsecureKeyPermissionsError.new(path, format("0%o", mode))
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+ require "yaml"
5
+
6
+ module RailsOutofbandKeys
7
+ # Railtie to integrate with Rails and automatically set key_path.
8
+ class Railtie < Rails::Railtie
9
+ config.rails_outofband_keys = RailsOutofbandKeys::Config.new
10
+
11
+ config.before_configuration do |app|
12
+ # Load file-based configuration.
13
+ config_file = File.join(Dir.getwd, "config", "rails_outofband_keys.yml")
14
+
15
+ if File.exist?(config_file)
16
+ external_config = YAML.load_file(config_file)
17
+ app.config.rails_outofband_keys.root_subdir = external_config["root_subdir"]
18
+ app.config.rails_outofband_keys.credentials_subdir = external_config.fetch("credentials_subdir", "credentials")
19
+ end
20
+
21
+ # Identify the app name for path resolution.
22
+ app_name = app.railtie_name.to_s.underscore.sub(/_application$/, "")
23
+
24
+ resolver = KeyResolver.new(
25
+ config: app.config.rails_outofband_keys,
26
+ rails_env: Rails.env,
27
+ app_name: app_name
28
+ )
29
+
30
+ key_path = resolver.resolve_key_path
31
+ if key_path
32
+ app.config.credentials.key_path = key_path
33
+
34
+ # Clear any early-cached credentials object to ensure the new path is used.
35
+ app.remove_instance_variable(:@credentials) if app.instance_variable_defined?(:@credentials)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOutofbandKeys
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rails_outofband_keys/version"
4
+ require_relative "rails_outofband_keys/errors"
5
+ require_relative "rails_outofband_keys/config"
6
+ require_relative "rails_outofband_keys/key_resolver"
7
+ require_relative "rails_outofband_keys/railtie"
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_outofband_keys
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Lori Holden
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: xdg
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2.2'
40
+ description: |
41
+ Configures Rails credentials key_path to load environment/master key files
42
+ from an out-of-band directory (XDG on Unix/MacOS, AppData on Windows).
43
+ email:
44
+ - git@loriholden.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - README.md
50
+ - lib/rails_outofband_keys.rb
51
+ - lib/rails_outofband_keys/config.rb
52
+ - lib/rails_outofband_keys/errors.rb
53
+ - lib/rails_outofband_keys/key_resolver.rb
54
+ - lib/rails_outofband_keys/railtie.rb
55
+ - lib/rails_outofband_keys/version.rb
56
+ homepage: https://github.com/lholden/rails_outofband_keys
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ rubygems_mfa_required: 'true'
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 4.0.2
76
+ specification_version: 4
77
+ summary: Resolve Rails credentials key files outside the project tree (XDG/AppData
78
+ + overrides).
79
+ test_files: []