secret_keys 1.0.1 → 1.0.3

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: ea7b02c543a0c676946de57cf6b5614a896dd0306d2a9ca6c970369e29d9eb30
4
- data.tar.gz: 99635a343094bf346d73368739e1ab66c2bfbe1ecae0f1eda124a9f7c90bd379
3
+ metadata.gz: fd54d07d04fbd4695a6896fec897ee82171f27e10778aa59247dc2c216511149
4
+ data.tar.gz: 6faeb6462cce486f5f4e27619fe3e88e7f1587a1a51949821166ee3359419a0f
5
5
  SHA512:
6
- metadata.gz: 077fb2e1b46e7e62a9e3f54315e7e6a98bda74256ab16fa550a8a5cbc0efb34937d1359639f6e91d7102801e114cf079f70415ab091670b99b5c4fc9acf8b63a
7
- data.tar.gz: 3b640dfae9fc3f2ee60763a3bfebb17a12b5a0428743d788d8151c49d7916e2e80249ddcad3e150573d5ae72d9a68c26021b414c1bfb1e15ebfb366ac663c966
6
+ metadata.gz: e509e3f5b8fb49e5c7859f18cd73a620ca89d95c0e82631cdb40bf77366d5dba131107d09d2057236d8e44982a6a232497c799e5723727968b37f6c88f253d13
7
+ data.tar.gz: 772f82950d3efe42d597bc6918f10f8ed2664d6b5738a9af0bb8767fb59b8212a0420a88a5169ad35972600936aace607323921b9bdf80dedf0b66669a7c6e22
data/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## 1.0.3
8
+
9
+ ### Fixed
10
+
11
+ - Files are now written atomically (write to a temporary file and rename) so a crash or concurrent reader can never see a partially written secrets file. The mode of an existing file is preserved.
12
+ - New secrets files are now created readable and writable only by the owner (mode 0600) instead of relying on the process umask. The mode of an existing file is still preserved.
13
+ - `secret_keys init` now uses an exclusive file create so it can no longer overwrite a file created between the existence check and the write.
14
+ - `SecretKeys#save` no longer raises `ArgumentError` when passed a `Pathname`.
15
+ - An unsupported `.version` value that is a string now raises `SecretKeys::VersionError` instead of a comparison `ArgumentError`; numeric strings like `"1"` are accepted.
16
+ - Input that does not parse to a Hash now raises a clear `ArgumentError` instead of failing with confusing errors; input that parses to nothing (e.g. only comments) loads as an empty document.
17
+ - A non-Hash value in the `.encrypted` key now raises a clear `ArgumentError`.
18
+ - `SecretKeys#encryption_key=` now raises `SecretKeys::EncryptionKeyError` when given a nil or empty key instead of silently keeping the old key.
19
+ - The `--secret-key-file` CLI option now reports a missing file as a clean error instead of an unhandled `Errno::ENOENT`.
20
+
21
+ ## 1.0.2
22
+
23
+ ### Changed
24
+
25
+ - Follow [RFC 4648](https://www.ietf.org/rfc/rfc4648.txt) base 64 encoding, removing line-feeds from the encoded data.
26
+
27
+ ## 1.0.1 (June 01, 2020)
28
+
29
+ ### Fixed
30
+
31
+ - Fix missing documentation links
32
+
33
+ ## v1.0.0 (May 31, 2020)
34
+
35
+ ### Added
36
+
37
+ - Initial release
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
- # SecretKeys
1
+ # Secret Keys
2
2
 
3
- [![specs](https://github.com/bdurand/secret_keys/workflows/Run%20tests/badge.svg)](https://github.com/bdurand/secret_keys/actions?query=branch%3Amaster)
4
- [![code style](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
3
+ [![Continuous Integration](https://github.com/bdurand/secret_keys/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/secret_keys/actions/workflows/continuous_integration.yml)
4
+ [![Regression Test](https://github.com/bdurand/secret_keys/actions/workflows/regression_test.yml/badge.svg)](https://github.com/bdurand/secret_keys/actions/workflows/regression_test.yml)
5
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
5
6
  [![gem version](https://badge.fury.io/rb/secret_keys.svg)](https://badge.fury.io/rb/secret_keys)
7
+ [![Gem Version](https://badge.fury.io/rb/secret_keys.svg)](https://badge.fury.io/rb/secret_keys)
6
8
 
7
9
  This ruby gem handles encrypting values in a JSON or YAML file. It is yet another solution for storing secrets in a ruby project.
8
10
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.0.3
@@ -3,7 +3,7 @@
3
3
  require "optparse"
4
4
  require "io/console"
5
5
 
6
- require_relative "../secret_keys.rb"
6
+ require_relative "../secret_keys"
7
7
 
8
8
  module SecretKeys::CLI
9
9
  class Base
@@ -47,7 +47,7 @@ module SecretKeys::CLI
47
47
 
48
48
  def encrypted_file_contents
49
49
  encrypted = secrets.encrypted_hash
50
- string = (format == :yaml ? YAML.dump(encrypted) : JSON.pretty_generate(encrypted))
50
+ string = ((format == :yaml) ? YAML.dump(encrypted) : JSON.pretty_generate(encrypted))
51
51
  string << $/ unless string.end_with?($/) # ensure file ends with system dependent new line
52
52
  string
53
53
  end
@@ -78,6 +78,7 @@ module SecretKeys::CLI
78
78
  HELP
79
79
  opts.on("--secret-key-file=PATH", String, *secret_file_docs) do |value|
80
80
  raise ArgumentError, "You have already passed in the secret key" unless @secret_key.nil?
81
+ raise ArgumentError, "Secret key file '#{value}' does not exist" unless File.exist?(value)
81
82
  @secret_key = File.read(value).chomp
82
83
  end
83
84
 
@@ -165,13 +166,19 @@ module SecretKeys::CLI
165
166
  def run!
166
167
  @secrets = SecretKeys.new({}, secret_key)
167
168
  if input.is_a?(String)
168
- if File.exist?(input)
169
- STDERR.puts "Error: Cannot init preexisting file '#{input}'"
170
- STDERR.puts "You may want to try calling `secret_keys encrypt/edit` instead"
169
+ contents = encrypted_file_contents
170
+ begin
171
+ # Open with exclusive create so a file created by another process
172
+ # between now and the write can never be clobbered. The file is
173
+ # created readable and writable only by the owner.
174
+ File.open(input, File::WRONLY | File::CREAT | File::EXCL, 0o600) do |file|
175
+ file.write(contents)
176
+ end
177
+ rescue Errno::EEXIST
178
+ warn "Error: Cannot init preexisting file '#{input}'"
179
+ warn "You may want to try calling `secret_keys encrypt/edit` instead"
171
180
  exit 1
172
181
  end
173
-
174
- File.write(input, encrypted_file_contents)
175
182
  else
176
183
  $stdout.write(encrypted_file_contents)
177
184
  end
@@ -220,9 +227,7 @@ module SecretKeys::CLI
220
227
  raise ArgumentError, "Cannot perform in place editing on streams" unless @input.is_a?(String)
221
228
  # make sure we read the file **before** writing to it.
222
229
  contents = encrypted_file_contents
223
- File.open(@input, "w") do |file|
224
- file.write(contents)
225
- end
230
+ SecretKeys.atomic_write(@input, contents)
226
231
  else
227
232
  $stdout.write(encrypted_file_contents)
228
233
  $stdout.flush
@@ -237,7 +242,7 @@ module SecretKeys::CLI
237
242
 
238
243
  def run!
239
244
  decrypted = secrets.to_h
240
- string = (format == :yaml ? YAML.dump(decrypted) : JSON.pretty_generate(decrypted))
245
+ string = ((format == :yaml) ? YAML.dump(decrypted) : JSON.pretty_generate(decrypted))
241
246
  string << $/ unless string.end_with?($/) # ensure file ends with system dependent new line
242
247
  $stdout.write(string)
243
248
  $stdout.flush
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "securerandom"
4
4
  require "openssl"
5
- require "base64"
6
5
 
7
6
  # Encyption helper for encrypting and decrypting values using AES-256-GCM and returning
8
7
  # as Base64 encoded strings. The encrypted values also include a prefix that can be used
@@ -22,8 +21,8 @@ class SecretKeys::Encryptor
22
21
  class << self
23
22
  # Create an Encryptor from a password and salt. This is a shortcut for generating an Encryptor
24
23
  # with a 32 byte encryption key. The key will be derived from the password and salt.
25
- # @param [String] password secret used to encrypt the data
26
- # @param [String] salt random hex-encoded byte array for key derivation
24
+ # @param password [String] secret used to encrypt the data
25
+ # @param salt [String] random hex-encoded byte array for key derivation
27
26
  # @return [SecretKeys::Encryptor] a new encryptor with key derived from password and salt
28
27
  def from_password(password, salt)
29
28
  raise ArgumentError, "Password must be present" if password.nil? || password.empty?
@@ -56,7 +55,7 @@ class SecretKeys::Encryptor
56
55
  end
57
56
  end
58
57
 
59
- # @param [String] raw_key the key directly passed into the encrypt/decrypt functions. This must be exactly {KEY_LENGTH} bytes long.
58
+ # @param raw_key [String] the key directly passed into the encrypt/decrypt functions. This must be exactly {KEY_LENGTH} bytes long.
60
59
  def initialize(raw_key)
61
60
  raise ArgumentError, "key must be #{KEY_LENGTH} bytes long" unless raw_key.bytesize == KEY_LENGTH
62
61
  @derived_key = raw_key
@@ -66,7 +65,7 @@ class SecretKeys::Encryptor
66
65
  # calling this function multiple times will result in different values. Only strings
67
66
  # can be encrypted. Any other object type will be return the value passed in.
68
67
  #
69
- # @param [String] str string to encrypt (assumes UTF-8)
68
+ # @param str [String] string to encrypt (assumes UTF-8)
70
69
  # @return [String] Base64 encoded encrypted string with all aes parameters
71
70
  def encrypt(str)
72
71
  return str unless str.is_a?(String)
@@ -97,13 +96,13 @@ class SecretKeys::Encryptor
97
96
  # Decrypt a string with the encryption key. If the value is not a string or it was
98
97
  # not encrypted with the encryption key, the value itself will be returned.
99
98
  #
100
- # @param [String] encrypted_str Base64 encoded encrypted string with aes params (from {#encrypt})
99
+ # @param encrypted_str [String] Base64 encoded encrypted string with aes params (from {#encrypt})
101
100
  # @return [String] decrypted string value
102
101
  # @raise [OpenSSL::Cipher::CipherError] there is something wrong with the encoded data (usually incorrect key)
103
102
  def decrypt(encrypted_str)
104
103
  return encrypted_str unless self.class.encrypted?(encrypted_str)
105
104
 
106
- decrypt_str = encrypted_str[ENCRYPTED_PREFIX.length..-1]
105
+ decrypt_str = encrypted_str[ENCRYPTED_PREFIX.length..]
107
106
  params = decode_aes(decrypt_str)
108
107
 
109
108
  cipher = OpenSSL::Cipher.new(CIPHER).decrypt
@@ -138,13 +137,13 @@ class SecretKeys::Encryptor
138
137
  # Receive a cipher object (initialized with key) and data
139
138
  def encode_aes(params)
140
139
  encoded = params.values.pack(ENCODING_FORMAT)
141
- # encode base64 and get rid of trailing newline and unnecessary =
142
- Base64.encode64(encoded).chomp.tr("=", "")
140
+ # encode base64 and get rid of unnecessary '=' padding
141
+ [encoded].pack("m0").tr("=", "")
143
142
  end
144
143
 
145
144
  # Passed in an aes encoded string and returns a cipher object
146
145
  def decode_aes(str)
147
- unpacked_data = Base64.decode64(str).unpack(ENCODING_FORMAT)
146
+ unpacked_data = str.unpack1("m").unpack(ENCODING_FORMAT)
148
147
  # Splat the data array apart
149
148
  # nonce, auth_tag, encrypted_data = unpacked_data
150
149
  CipherParams.new(*unpacked_data)
data/lib/secret_keys.rb CHANGED
@@ -15,13 +15,42 @@ class SecretKeys < DelegateClass(Hash)
15
15
  # Error if the crypto version specified in the file is unsupported
16
16
  class VersionError < StandardError; end
17
17
 
18
+ # Write a file atomically by writing to a temporary file in the same directory and
19
+ # renaming it over the target path so that readers never see a partially written file.
20
+ # The mode of an existing file at the path will be preserved. New files are created
21
+ # readable and writable only by the owner.
22
+ #
23
+ # @api private
24
+ # @param path [String, Pathname] path of the file to write
25
+ # @param content [String] contents of the file
26
+ # @return [void]
27
+ def self.atomic_write(path, content)
28
+ path = File.expand_path(path.to_s)
29
+ mode = begin
30
+ File.stat(path).mode & 0o7777
31
+ rescue Errno::ENOENT
32
+ 0o600
33
+ end
34
+ tmp_path = File.join(File.dirname(path), ".#{File.basename(path)}.tmp.#{Process.pid}.#{rand(1_000_000)}")
35
+ begin
36
+ File.open(tmp_path, "w") do |file| # rubocop:disable Style/FileWrite
37
+ file.write(content)
38
+ end
39
+ File.chmod(mode, tmp_path)
40
+ File.rename(tmp_path, path)
41
+ ensure
42
+ File.unlink(tmp_path) if File.exist?(tmp_path)
43
+ end
44
+ nil
45
+ end
46
+
18
47
  # Parse a JSON or YAML stream or file with encrypted values. Any values in the ".encrypted" key
19
48
  # in the document will be decrypted with the provided encryption key. If values
20
49
  # were put into the ".encrypted" key manually and are not yet encrypted, they will be used
21
50
  # as is without any decryption.
22
51
  #
23
- # @param [String, #read, Hash] path_or_stream path to a JSON/YAML file to load, an IO object, or a Hash (mostly for testing purposes)
24
- # @param [String] encryption_key secret to use for encryption/decryption
52
+ # @param path_or_stream [String, #read, Hash] path to a JSON/YAML file to load, an IO object, or a Hash (mostly for testing purposes)
53
+ # @param encryption_key [String] secret to use for encryption/decryption
25
54
  #
26
55
  # @note If no encryption key is passed, this will defautl to env var SECRET_KEYS_ENCRYPTION_KEY
27
56
  # or (if that is empty) the value read from the file path in SECRET_KEYS_ENCRYPTION_KEY_FILE.
@@ -44,11 +73,11 @@ class SecretKeys < DelegateClass(Hash)
44
73
  def to_h
45
74
  @values
46
75
  end
47
- alias to_hash to_h
76
+ alias_method :to_hash, :to_h
48
77
 
49
78
  # Mark the key as being encrypted when the JSON is saved.
50
79
  #
51
- # @param [String] key key to mark as needing encryption
80
+ # @param key [String] key to mark as needing encryption
52
81
  # @return [void]
53
82
  def encrypt!(key)
54
83
  @secret_keys << key
@@ -57,7 +86,7 @@ class SecretKeys < DelegateClass(Hash)
57
86
 
58
87
  # Mark the key as no longer being decrypted when the JSON is saved.
59
88
  #
60
- # @param [String] key key to mark as not needing encryption
89
+ # @param key [String] key to mark as not needing encryption
61
90
  # @return [void]
62
91
  def decrypt!(key)
63
92
  @secret_keys.delete(key)
@@ -66,7 +95,7 @@ class SecretKeys < DelegateClass(Hash)
66
95
 
67
96
  # Return true if the key is encrypted.
68
97
  #
69
- # @param [String] key key to check
98
+ # @param key [String] key to check
70
99
  # @return [Boolean]
71
100
  def encrypted?(key)
72
101
  @secret_keys.include?(key)
@@ -77,8 +106,8 @@ class SecretKeys < DelegateClass(Hash)
77
106
  # different initialization vector). This can be helpful if you have your secrets in source
78
107
  # control so that only changed keys will actually be changed in the file when it is updated.
79
108
  #
80
- # @param [String, Pathname] path path of the file to save. If the file exists, only changed values will be updated.
81
- # @param [String, Symbol] format: output format (YAML or JSON) to use. This will default based on the extension on the file path or the format originally used
109
+ # @param path [String, Pathname] path of the file to save. If the file exists, only changed values will be updated.
110
+ # @param format [String, Symbol] output format (YAML or JSON) to use. This will default based on the extension on the file path or the format originally used
82
111
  # @return [void]
83
112
  def save(path, format: nil)
84
113
  # create a copy of the encrypted hash for working on
@@ -94,11 +123,9 @@ class SecretKeys < DelegateClass(Hash)
94
123
  format ||= @format
95
124
  format = format.to_s.downcase
96
125
 
97
- output = (format == "yaml" ? YAML.dump(encrypted) : JSON.pretty_generate(encrypted))
126
+ output = ((format == "yaml") ? YAML.dump(encrypted) : JSON.pretty_generate(encrypted))
98
127
  output << $/ unless output.end_with?($/) # ensure file ends with system dependent new line
99
- File.open(path, "w") do |file|
100
- file.write(output)
101
- end
128
+ self.class.atomic_write(path, output)
102
129
  nil
103
130
  end
104
131
 
@@ -133,9 +160,10 @@ class SecretKeys < DelegateClass(Hash)
133
160
 
134
161
  # Change the encryption key in the document. When saving later, this key will be used.
135
162
  #
136
- # @param [String] new_encryption_key encryption key to use for future {#save} calls
163
+ # @param new_encryption_key [String] encryption key to use for future {#save} calls
137
164
  # @return [void]
138
165
  def encryption_key=(new_encryption_key)
166
+ raise EncryptionKeyError.new("Encryption key not specified") if new_encryption_key.nil? || new_encryption_key.empty?
139
167
  @original_encrypted = {}
140
168
  update_secret(key: new_encryption_key)
141
169
  end
@@ -168,10 +196,10 @@ class SecretKeys < DelegateClass(Hash)
168
196
 
169
197
  hash = {}
170
198
  if path_or_stream.is_a?(Hash)
171
- # HACK: Perform a marshal dump/load operation to get a deep copy of the hash.
172
- # Otherwise, we can end up using destructive `#delete` operations and mess
173
- # up deeply nested values for external code (esp. when loading key: .encrypted)
174
- hash = Marshal.load(Marshal.dump(path_or_stream))
199
+ # Deep copy the hash. Otherwise, we can end up using destructive `#delete`
200
+ # operations and mess up deeply nested values for external code
201
+ # (esp. when loading key: .encrypted)
202
+ hash = deep_dup(path_or_stream)
175
203
  elsif path_or_stream
176
204
  data = path_or_stream.read
177
205
  hash = parse_data(data)
@@ -179,20 +207,31 @@ class SecretKeys < DelegateClass(Hash)
179
207
 
180
208
  encrypted_values = hash.delete(ENCRYPTED)
181
209
  if encrypted_values
182
- @original_encrypted = Marshal.load(Marshal.dump(encrypted_values))
210
+ raise ArgumentError, "The #{ENCRYPTED} key must contain a Hash of encrypted values" unless encrypted_values.is_a?(Hash)
211
+
212
+ @original_encrypted = deep_dup(encrypted_values)
183
213
 
184
214
  version = encrypted_values.delete(VERSION_KEY) || CRYPTO_VERSION
185
- raise VersionError, "Unsupported file version #{version}. Max supported is #{CRYPTO_VERSION}." if version > CRYPTO_VERSION
215
+ if version.is_a?(String)
216
+ version = begin
217
+ Integer(version, 10)
218
+ rescue ArgumentError
219
+ version
220
+ end
221
+ end
222
+ unless version.is_a?(Numeric) && version <= CRYPTO_VERSION
223
+ raise VersionError, "Unsupported file version #{version}. Max supported is #{CRYPTO_VERSION}."
224
+ end
186
225
 
187
226
  file_key = encrypted_values.delete(ENCRYPTION_KEY)
188
- salt = (encrypted_values.delete(SALT) || Encryptor.random_salt)
227
+ salt = encrypted_values.delete(SALT) || Encryptor.random_salt
189
228
  update_secret(salt: salt)
190
229
 
191
230
  # Check that we are using the right key
192
231
  if file_key && !encryption_key_matches?(file_key)
193
232
  raise EncryptionKeyError.new("Incorrect encryption key")
194
233
  end
195
- @secret_keys = encrypted_values.keys
234
+ @secret_keys = Set.new(encrypted_values.keys)
196
235
  hash.merge!(decrypt_values(encrypted_values))
197
236
  elsif @salt.nil?
198
237
  # if no salt exists, create one.
@@ -203,15 +242,38 @@ class SecretKeys < DelegateClass(Hash)
203
242
  end
204
243
 
205
244
  # Attempt to parse the file first using JSON and fallback to YAML
206
- # @param [String] data file data to parse
245
+ # @param data [String] file data to parse
207
246
  # @return [Hash] data parsed to a hash
208
247
  def parse_data(data)
209
248
  @format = :json
210
249
  return {} if data.nil? || data.empty?
211
- JSON.parse(data)
212
- rescue JSON::JSONError
213
- @format = :yaml
214
- YAML.safe_load(data)
250
+ hash = begin
251
+ JSON.parse(data)
252
+ rescue JSON::JSONError
253
+ @format = :yaml
254
+ YAML.safe_load(data)
255
+ end
256
+ # YAML.safe_load returns false instead of nil for empty documents on Ruby < 3.4.
257
+ return {} if hash.nil? || hash == false
258
+ raise ArgumentError, "Data must parse to a Hash of key value pairs" unless hash.is_a?(Hash)
259
+ hash
260
+ end
261
+
262
+ # Deep copy a parsed data structure of hashes, arrays, and scalar values.
263
+ def deep_dup(value)
264
+ if value.is_a?(Hash)
265
+ copy = {}
266
+ value.each do |key, val|
267
+ copy[deep_dup(key)] = deep_dup(val)
268
+ end
269
+ copy
270
+ elsif value.is_a?(Array)
271
+ value.collect { |val| deep_dup(val) }
272
+ elsif value.is_a?(String)
273
+ value.dup
274
+ else
275
+ value
276
+ end
215
277
  end
216
278
 
217
279
  # Recursively encrypt all values.
@@ -265,23 +327,6 @@ class SecretKeys < DelegateClass(Hash)
265
327
  @encryptor.decrypt(encrypted_value)
266
328
  end
267
329
 
268
- # Helper method to test if two values are both encrypted, but result in the same decrypted value.
269
- def equal_encrypted_values?(value_1, value_2)
270
- return true if value_1 == value_2
271
-
272
- decrypt_val_1 = decrypt_value(value_1)
273
- decrypt_val_2 = decrypt_value(value_2)
274
- if decrypt_val_1 == decrypt_val_2
275
- if value_1 == decrypt_val_1 || value_2 == decrypt_val_2
276
- false
277
- else
278
- true
279
- end
280
- else
281
- false
282
- end
283
- end
284
-
285
330
  # This is an encrypted known value that can be used determine if the secret has changed.
286
331
  def encrypted_known_value
287
332
  encrypt_value(KNOWN_VALUE)
@@ -296,13 +341,12 @@ class SecretKeys < DelegateClass(Hash)
296
341
  end
297
342
 
298
343
  def yaml_file?(path)
299
- ext = path.split(".").last.to_s.downcase
300
- ext == "yaml" || ext == "yml"
344
+ ext = File.extname(path.to_s).downcase
345
+ ext == ".yaml" || ext == ".yml"
301
346
  end
302
347
 
303
348
  def json_file?(path)
304
- ext = path.split(".").last.to_s.downcase
305
- ext == "json"
349
+ File.extname(path.to_s).downcase == ".json"
306
350
  end
307
351
 
308
352
  # Update the secret key by updating the salt
data/secret_keys.gemspec CHANGED
@@ -7,9 +7,12 @@ Gem::Specification.new do |spec|
7
7
  spec.summary = "Simple mechanism for loading JSON file with encrypted values."
8
8
  spec.homepage = "https://github.com/bdurand/secret_keys"
9
9
  spec.license = "MIT"
10
- spec.metadata["homepage_uri"] = spec.homepage
11
- spec.metadata["source_code_uri"] = "https://github.com/bdurand/secret_keys/tree/v#{spec.version}"
12
- spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/secret_keys"
10
+
11
+ spec.metadata = {
12
+ "homepage_uri" => spec.homepage,
13
+ "source_code_uri" => spec.homepage,
14
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md"
15
+ }
13
16
 
14
17
  # Specify which files should be added to the gem when it is released.
15
18
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -17,6 +20,7 @@ Gem::Specification.new do |spec|
17
20
  .
18
21
  Appraisals
19
22
  Gemfile
23
+ Gemfile.lock
20
24
  Rakefile
21
25
  gemfiles/
22
26
  spec/
@@ -29,7 +33,7 @@ Gem::Specification.new do |spec|
29
33
  spec.bindir = "bin"
30
34
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
31
35
 
32
- spec.required_ruby_version = ">= 2.4"
36
+ spec.required_ruby_version = ">= 2.6"
33
37
 
34
- spec.add_development_dependency "bundler", "~>2.0"
38
+ spec.add_development_dependency "bundler"
35
39
  end
metadata CHANGED
@@ -1,31 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secret_keys
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  - Winston Durand
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2020-06-01 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: bundler
16
15
  requirement: !ruby/object:Gem::Requirement
17
16
  requirements:
18
- - - "~>"
17
+ - - ">="
19
18
  - !ruby/object:Gem::Version
20
- version: '2.0'
19
+ version: '0'
21
20
  type: :development
22
21
  prerelease: false
23
22
  version_requirements: !ruby/object:Gem::Requirement
24
23
  requirements:
25
- - - "~>"
24
+ - - ">="
26
25
  - !ruby/object:Gem::Version
27
- version: '2.0'
28
- description:
26
+ version: '0'
29
27
  email:
30
28
  - bbdurand@gmail.com
31
29
  - me@winstondurand.com
@@ -34,7 +32,7 @@ executables:
34
32
  extensions: []
35
33
  extra_rdoc_files: []
36
34
  files:
37
- - CHANGE_LOG.md
35
+ - CHANGELOG.md
38
36
  - MIT_LICENSE.txt
39
37
  - README.md
40
38
  - VERSION
@@ -49,9 +47,8 @@ licenses:
49
47
  - MIT
50
48
  metadata:
51
49
  homepage_uri: https://github.com/bdurand/secret_keys
52
- source_code_uri: https://github.com/bdurand/secret_keys/tree/v1.0.1
53
- documentation_uri: https://www.rubydoc.info/gems/secret_keys
54
- post_install_message:
50
+ source_code_uri: https://github.com/bdurand/secret_keys
51
+ changelog_uri: https://github.com/bdurand/secret_keys/blob/main/CHANGELOG.md
55
52
  rdoc_options: []
56
53
  require_paths:
57
54
  - lib
@@ -59,15 +56,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
59
56
  requirements:
60
57
  - - ">="
61
58
  - !ruby/object:Gem::Version
62
- version: '2.4'
59
+ version: '2.6'
63
60
  required_rubygems_version: !ruby/object:Gem::Requirement
64
61
  requirements:
65
62
  - - ">="
66
63
  - !ruby/object:Gem::Version
67
64
  version: '0'
68
65
  requirements: []
69
- rubygems_version: 3.1.2
70
- signing_key:
66
+ rubygems_version: 4.0.3
71
67
  specification_version: 4
72
68
  summary: Simple mechanism for loading JSON file with encrypted values.
73
69
  test_files: []
data/CHANGE_LOG.md DELETED
@@ -1,9 +0,0 @@
1
- # Changelog
2
-
3
- ## v1.0.1 (June 01, 2020)
4
-
5
- - Fix missing documentation links
6
-
7
- ## v1.0.0 (May 31, 2020)
8
-
9
- Initial release