Class: SecretKeys
- Inherits:
-
Hash
- Object
- Hash
- SecretKeys
- Defined in:
- lib/secret_keys.rb,
lib/secret_keys/version.rb
Overview
Load a JSON file with encrypted values. This value can be used as a hash.
Defined Under Namespace
Modules: CLI Classes: EncryptionKeyError, Encryptor, VersionError
Constant Summary collapse
- VERSION =
File.read(File.join(__dir__, "..", "..", "VERSION")).chomp.freeze
- CRYPTO_VERSION =
1
Class Method Summary collapse
-
.atomic_write(path, content) ⇒ void
private
Write a file atomically by writing to a temporary file in the same directory and renaming it over the target path so that readers never see a partially written file.
Instance Method Summary collapse
-
#decrypt!(key) ⇒ void
Mark the key as no longer being decrypted when the JSON is saved.
-
#encrypt!(key) ⇒ void
Mark the key as being encrypted when the JSON is saved.
-
#encrypted?(key) ⇒ Boolean
Return true if the key is encrypted.
-
#encrypted_hash ⇒ Hash
Output the keys as a hash that matches the structure that can be loaded by the initalizer.
-
#encryption_key=(new_encryption_key) ⇒ void
Change the encryption key in the document.
-
#initialize(path_or_stream, encryption_key = nil) ⇒ SecretKeys
constructor
Parse a JSON or YAML stream or file with encrypted values.
-
#input_format ⇒ String
Return the data format (:json or :yaml) for the original data.
-
#save(path, format: nil) ⇒ void
Save the encrypted hash to a file at the specified path.
-
#to_h ⇒ Hash
(also: #to_hash)
Convert into an actual Hash object.
Constructor Details
#initialize(path_or_stream, encryption_key = nil) ⇒ SecretKeys
If no encryption key is passed, this will defautl to env var SECRET_KEYS_ENCRYPTION_KEY
Parse a JSON or YAML stream or file with encrypted values. Any values in the ".encrypted" key in the document will be decrypted with the provided encryption key. If values were put into the ".encrypted" key manually and are not yet encrypted, they will be used as is without any decryption.
or (if that is empty) the value read from the file path in SECRET_KEYS_ENCRYPTION_KEY_FILE.
57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/secret_keys.rb', line 57 def initialize(path_or_stream, encryption_key = nil) @encryption_key = nil @salt = nil @format = :json encryption_key = read_encryption_key(encryption_key) update_secret(key: encryption_key) path_or_stream = Pathname.new(path_or_stream) if path_or_stream.is_a?(String) load_secrets!(path_or_stream) super(@values) end |
Class Method Details
.atomic_write(path, content) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Write a file atomically by writing to a temporary file in the same directory and renaming it over the target path so that readers never see a partially written file. The mode of an existing file at the path will be preserved. New files are created readable and writable only by the owner.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/secret_keys.rb', line 27 def self.atomic_write(path, content) path = File.(path.to_s) mode = begin File.stat(path).mode & 0o7777 rescue Errno::ENOENT 0o600 end tmp_path = File.join(File.dirname(path), ".#{File.basename(path)}.tmp.#{Process.pid}.#{rand(1_000_000)}") begin File.open(tmp_path, "w") do |file| # rubocop:disable Style/FileWrite file.write(content) end File.chmod(mode, tmp_path) File.rename(tmp_path, path) ensure File.unlink(tmp_path) if File.exist?(tmp_path) end nil end |
Instance Method Details
#decrypt!(key) ⇒ void
This method returns an undefined value.
Mark the key as no longer being decrypted when the JSON is saved.
91 92 93 94 |
# File 'lib/secret_keys.rb', line 91 def decrypt!(key) @secret_keys.delete(key) nil end |
#encrypt!(key) ⇒ void
This method returns an undefined value.
Mark the key as being encrypted when the JSON is saved.
82 83 84 85 |
# File 'lib/secret_keys.rb', line 82 def encrypt!(key) @secret_keys << key nil end |
#encrypted?(key) ⇒ Boolean
Return true if the key is encrypted.
100 101 102 |
# File 'lib/secret_keys.rb', line 100 def encrypted?(key) @secret_keys.include?(key) end |
#encrypted_hash ⇒ Hash
Output the keys as a hash that matches the structure that can be loaded by the initalizer. Values that have not changed will not be re-salted so the encrypted values will remain the same.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/secret_keys.rb', line 136 def encrypted_hash raise EncryptionKeyError.new("Encryption key not specified") if @encryption_key.nil? || @encryption_key.empty? hash = {} encrypted = {} @values.each do |key, value| if @secret_keys.include?(key) encrypted[key] = value else hash[key] = value end end unless encryption_key_matches?(@original_encrypted[ENCRYPTION_KEY]) @original_encrypted = {} end encrypted.merge!(encrypt_values(encrypted, @original_encrypted)) encrypted[SALT] = @salt encrypted[ENCRYPTION_KEY] = (@original_encrypted[ENCRYPTION_KEY] || encrypted_known_value) encrypted[VERSION_KEY] = CRYPTO_VERSION hash[ENCRYPTED] = encrypted hash end |
#encryption_key=(new_encryption_key) ⇒ void
This method returns an undefined value.
Change the encryption key in the document. When saving later, this key will be used.
165 166 167 168 169 |
# File 'lib/secret_keys.rb', line 165 def encryption_key=(new_encryption_key) raise EncryptionKeyError.new("Encryption key not specified") if new_encryption_key.nil? || new_encryption_key.empty? @original_encrypted = {} update_secret(key: new_encryption_key) end |
#input_format ⇒ String
Return the data format (:json or :yaml) for the original data. Defaults to :json.
174 175 176 |
# File 'lib/secret_keys.rb', line 174 def input_format @format end |
#save(path, format: nil) ⇒ void
This method returns an undefined value.
Save the encrypted hash to a file at the specified path. Encrypted values in an existing file will not be updated if the values have not changed (since each call uses a different initialization vector). This can be helpful if you have your secrets in source control so that only changed keys will actually be changed in the file when it is updated.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/secret_keys.rb', line 112 def save(path, format: nil) # create a copy of the encrypted hash for working on encrypted = encrypted_hash if format.nil? if yaml_file?(path) format = :yaml elsif json_file?(path) format = :json end end format ||= @format format = format.to_s.downcase output = ((format == "yaml") ? YAML.dump(encrypted) : JSON.pretty_generate(encrypted)) output << $/ unless output.end_with?($/) # ensure file ends with system dependent new line self.class.atomic_write(path, output) nil end |
#to_h ⇒ Hash Also known as: to_hash
Convert into an actual Hash object.
73 74 75 |
# File 'lib/secret_keys.rb', line 73 def to_h @values end |