dry-credentials 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24deb6ca2d926a11c530c6e81821af802fe1ac36e4fafb8c2273619afdd4ff8e
4
- data.tar.gz: 5be212ecb261ab3134b80f14d93a8ca9b7492be642b7a129b16239b0b727410f
3
+ metadata.gz: 2d4a73f1a9d55f7e32296e39d78e7ff5a6273226c25d10e97b0d2a10b9a00f1c
4
+ data.tar.gz: 56f39f76baf2a18dbc4bf163d5d12697687a2a026fcc9d01f6b632fef7dcf633
5
5
  SHA512:
6
- metadata.gz: a4e0f03fa13561dbc97b110b2d3f660889f0fe76d98615b981af07bf5b44481669f9b176692f77508d71d3629a8cf05c98262601cef5db4a5ee04b0d04de158a
7
- data.tar.gz: a0258c2537012e92dfa82ec88d1881a740d04eb9fae617fbafc79a3dabb9e0ff526e51856218a0316c5d99a1c357edb681d404cd2f831f7f79e183659de2dde3
6
+ metadata.gz: caac2559ad90fadce9f4b56923d556687d8da8c54eaf16de502e5c2598c10fbb49c57ef5acf8bbe7aaf87434e17bf66d816b25a2cd282ab920b1da5e45b25a21
7
+ data.tar.gz: a443919baad7138e75beedb23709a45526a986be9225ff6f4f3456cde71c41d03b220de32c6e27ab489839f35d5e467b54a9531f075c1cea80b41f4d64a25dd1
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -2,9 +2,26 @@
2
2
 
3
3
  Nothing so far
4
4
 
5
+ ## 0.2.1
6
+
7
+ ## 0.2.1
8
+
9
+ * Add square brackets setter for settings
10
+ * Explain integrations for Bridgetown, Hanami 2 and Rodbot
11
+
12
+ ## 0.2.0
13
+
14
+ #### Breaking Changes
15
+
16
+ * Fall back to `APP_ENV` instead of `RACK_ENV`
17
+
18
+ #### Fixes
19
+
20
+ * Don't re-encrypt if credentials haven't been modified
21
+
5
22
  ## 0.1.0
6
23
 
7
- #### Initial implementation
24
+ #### Initial Implementation
8
25
 
9
26
  * Require Ruby 3.0 or newer
10
27
  * Class mixin featuring the `credentials` macro:
data/README.md CHANGED
@@ -37,6 +37,8 @@ And then install the bundle:
37
37
  bundle install --trust-policy MediumSecurity
38
38
  ```
39
39
 
40
+ See [Integrations](#integrations) below for how to integrate Dry::Credentials into frameworks.
41
+
40
42
  ## Usage
41
43
 
42
44
  Extend any class with `Dry::Credentials` to use the [default settings](#defaults):
@@ -108,7 +110,7 @@ App.credentials.otp.meta.realm
108
110
 
109
111
  Credentials are isolated into environments which most likely will, but don't necessarily have to align with the environments of the app framework you're using.
110
112
 
111
- By default, the current environment is read from `RACK_ENV`.
113
+ By default, the current environment is read from `APP_ENV`. You shouldn't use `RACK_ENV` for this, [here's why](https://github.com/rack/rack/issues/1546).
112
114
 
113
115
  ⚠️ For safety reasons, don't share the same key across multiple environments!
114
116
 
@@ -148,12 +150,115 @@ App.credentials[:env] # => "production"
148
150
 
149
151
  Setting | Default | Description
150
152
  --------|---------|------------
151
- `env` | `-> { ENV["RACK_ENV"] }` | environment such as `development`
153
+ `env` | `-> { ENV["APP_ENV"] }` | environment such as `development`
152
154
  `dir` | `"config/credentials"` | directory where encrypted credentials are stored
153
155
  `cipher` | `"aes-256-gcm"` | any of `OpenSSL::Cipher.ciphers`
154
156
  `digest` | `"sha256"` | sign digest used if the cipher doesn't support AEAD
155
157
  `serializer` | `Marshal` | serializer responding to `dump` and `load`
156
158
 
159
+ ## Integrations
160
+
161
+ ### Bridgetown
162
+
163
+ The [bridgetown_credentials gem](https://github.com/svoop/bridgetown_credentials) integrates Dry::Credentials into your [Bridgetown](https://www.bridgetownrb.com) site.
164
+
165
+ ### Hanami 2
166
+
167
+ To use credentials in a [Hanami 2](https//hanami.org) app, first add this gem to the gemfile of the app and then create a provider `config/providers/credentials.rb`:
168
+
169
+ ```ruby
170
+ # frozen_string_literal: true
171
+
172
+ Hanami.app.register_provider :credentials do
173
+ prepare do
174
+ require "dry-credentials"
175
+
176
+ Dry::Credentials::Extension.new.then do |credentials|
177
+ credentials[:env] = Hanami.env
178
+ credentials.load!
179
+ register "credentials", credentials
180
+ end
181
+ end
182
+ end
183
+ ```
184
+
185
+ You might want to add a Rake task `lib/tasks/credentials.rake` as well:
186
+
187
+ ```ruby
188
+ namespace :credentials do
189
+ desc "Edit (or create) the encrypted credentials file"
190
+ task :edit, [:env] => [:environment] do |_, args|
191
+ Hanami.app.prepare(:credentials)
192
+ Hanami.app['credentials'].edit! args[:env]
193
+ end
194
+ end
195
+ ```
196
+
197
+ (As of Hanami 2.1, you have to [explicitly load such tasks in the Rakefile](https://github.com/hanami/hanami/issues/1375) yourself.)
198
+
199
+ You can now create a new credentials file for the development environment:
200
+
201
+ ```
202
+ rake credentials:edit
203
+ ```
204
+
205
+ This prints the credentials key you have to set in `.env`:
206
+
207
+ ```
208
+ DEVELOPMENT_CREDENTIALS_KEY=...
209
+ ```
210
+
211
+ The credentials are now available anywhere you inject them:
212
+
213
+ ```ruby
214
+ module MyHanamiApp
215
+ class ApiKeyPrinter
216
+ include Deps[
217
+ "credentials"
218
+ ]
219
+
220
+ def call
221
+ puts credentials.api_key
222
+ end
223
+ end
224
+ end
225
+ ```
226
+
227
+ You can use the credentials in other providers. Say, you want to pass the [ROM](https://rom-rb.org/) database URL (which contains the connection password) using credentials instead of settings. Simply replace `target["settings"].database_url` with `target["credentials"].database_url` and you're good to go:
228
+
229
+ ```ruby
230
+ Hanami.app.register_provider :persistence, namespace: true do
231
+ prepare do
232
+ require "rom"
233
+
234
+ config = ROM::Configuration.new(:sql, target["credentials"].database_url)
235
+
236
+ register "config", config
237
+ register "db", config.gateways[:default].connection
238
+ end
239
+
240
+ (...)
241
+ end
242
+ ```
243
+
244
+ Finally, if you have trouble using the credentials in slices, you might have to [share this app component](https://www.rubydoc.info/gems/hanami/Hanami/Config#shared_app_component_keys-instance_method) in `config/app.rb`:
245
+
246
+ ```ruby
247
+ module MyHanamiApp
248
+ class App < Hanami::App
249
+ config.shared_app_component_keys += ["credentials"]
250
+ end
251
+ end
252
+ ```
253
+
254
+ ### Ruby on Rails
255
+
256
+ ActiveSupport implements [encrypted configuration](https://www.rubydoc.info/gems/activesupport/ActiveSupport/EncryptedConfiguration) which is used by `rails credentials:edit` [out of the box]((https://guides.rubyonrails.org/security.html#custom-credentials)). There's no benefit from introducing an additional dependency like Dry::Credentials.
257
+
258
+ ### Rodbot
259
+
260
+ Dry::Credentials is integrated into [Rodbot](https://github.com/svoop/rodbot) out of the box, see [the README for more](https://github.com/svoop/rodbot/blob/main/README.md#credentials).
261
+
157
262
  ## Development
158
263
 
159
264
  To install the development dependencies and then run the test suite:
@@ -164,4 +269,4 @@ bundle exec rake # run tests once
164
269
  bundle exec guard # run tests whenever files are modified
165
270
  ```
166
271
 
167
- You're welcome to [submit issues](https://github.com/svoop/dry-credentials/issues) and contribute code by [forking the project and submitting pull requests](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
272
+ You're welcome to join the [discussion forum](https://github.com/svoop/dry-credentials/discussions) to ask questions or drop feature ideas, [submit issues](https://github.com/svoop/dry-credentials/issues) you may encounter or contribute code by [forking this project and submitting pull requests](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
@@ -12,8 +12,6 @@ module Dry
12
12
  DEFAULT_SERIALIZER = Marshal
13
13
  SEPARATOR = '--'
14
14
 
15
- attr_reader :cipher
16
-
17
15
  # @param cipher [String] any of +OpenSSL::Cipher.ciphers+
18
16
  # @param digest [String] any of +openssl list+
19
17
  # @param serializer [Class] must respond to +dump+ and +load+
@@ -22,12 +20,12 @@ module Dry
22
20
  @digest, @serializer = digest, serializer
23
21
  end
24
22
 
25
- # Generate a random key with the length requird by the current cipher,
23
+ # Generate a random key with the length required by the current cipher,
26
24
  # then Base64 encodes and unpacks all bytes to hex.
27
25
  #
28
26
  # @return [String] key
29
27
  def generate_key
30
- unpack(encode(SecureRandom.bytes(cipher.key_len)))
28
+ unpack(encode(SecureRandom.bytes(@cipher.key_len)))
31
29
  end
32
30
 
33
31
  # Encrypts the object
@@ -39,15 +37,15 @@ module Dry
39
37
  # @param key [String] key (Base64 encoded and unpacked to hex)
40
38
  # @return [String] encrypted and authenticated/signed string
41
39
  def encrypt(object, key:)
42
- cipher.encrypt
43
- cipher.key = decoded_key = decode(pack(key.strip))
44
- iv = cipher.random_iv
45
- cipher.auth_data = '' if aead?
46
- cipher.update(@serializer.dump(object)).then do |data|
47
- data << cipher.final
40
+ @cipher.encrypt
41
+ @cipher.key = decoded_key = decode(pack(key.strip))
42
+ iv = @cipher.random_iv
43
+ @cipher.auth_data = '' if aead?
44
+ @cipher.update(@serializer.dump(object)).then do |data|
45
+ data << @cipher.final
48
46
  data = encode(data) + SEPARATOR + encode(iv)
49
47
  data << SEPARATOR + if aead?
50
- encode(cipher.auth_tag)
48
+ encode(@cipher.auth_tag)
51
49
  else
52
50
  hmac(decoded_key, data)
53
51
  end
@@ -60,8 +58,8 @@ module Dry
60
58
  # @param key [String] key (Base64 encoded and unpacked to hex)
61
59
  # @return [Object] verified and decrypted object
62
60
  def decrypt(encrypted_object, key:)
63
- cipher.decrypt
64
- cipher.key = decoded_key = decode(pack(key.strip))
61
+ @cipher.decrypt
62
+ @cipher.key = decoded_key = decode(pack(key.strip))
65
63
  payload, iv, auth_tag = encrypted_object.strip.split(SEPARATOR)
66
64
  if auth_tag.nil? ||
67
65
  (aead? && decode(auth_tag).bytes.length != auth_tag_length) ||
@@ -69,13 +67,13 @@ module Dry
69
67
  then
70
68
  fail Dry::Credentials::InvalidEncryptedObjectError
71
69
  end
72
- cipher.iv = decode(iv)
70
+ @cipher.iv = decode(iv)
73
71
  if aead?
74
- cipher.auth_tag = decode(auth_tag)
75
- cipher.auth_data = ''
72
+ @cipher.auth_tag = decode(auth_tag)
73
+ @cipher.auth_data = ''
76
74
  end
77
- cipher.update(decode(payload)).then do |data|
78
- data << cipher.final
75
+ @cipher.update(decode(payload)).then do |data|
76
+ data << @cipher.final
79
77
  @serializer.load(data)
80
78
  end
81
79
  rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError
@@ -112,7 +110,7 @@ module Dry
112
110
  # Associated Data) or not - in which case a HMAC signature using the
113
111
  # +digest+ is used instead
114
112
  def aead?
115
- @auth ||= cipher.authenticated?
113
+ @auth ||= @cipher.authenticated?
116
114
  end
117
115
 
118
116
  def hmac(key, string)
@@ -36,13 +36,15 @@ module Dry
36
36
  def edit!(env=nil)
37
37
  helpers = Dry::Credentials::Helpers.new(self, env)
38
38
  create = helpers.create?
39
- yaml = helpers.read_yaml
39
+ yaml = read_yaml = helpers.read_yaml
40
40
  begin
41
41
  yaml = helpers.edit_yaml yaml
42
42
  end until helpers.yaml_valid? yaml
43
- helpers.write_yaml yaml
44
- puts [helpers.key_ev, ENV[helpers.key_ev]].join('=') if create
45
- reload!
43
+ unless yaml == read_yaml
44
+ helpers.write_yaml yaml
45
+ puts [helpers.key_ev, ENV[helpers.key_ev]].join('=') if create
46
+ reload!
47
+ end
46
48
  end
47
49
 
48
50
  # Query settings
@@ -53,6 +55,14 @@ module Dry
53
55
  @settings.send(setting)
54
56
  end
55
57
 
58
+ # Change settings
59
+ #
60
+ # @param setting [String] name of the setting
61
+ # @param value [Object] new value of the setting
62
+ def []=(setting, value)
63
+ @settings.send(setting, value)
64
+ end
65
+
56
66
  end
57
67
  end
58
68
  end
@@ -75,7 +75,7 @@ module Dry
75
75
  if create?
76
76
  ENV[key_ev] = encryptor.generate_key
77
77
  else
78
- ENV[key_ev] or fail Dry::Credentials::KeyNotSetError
78
+ ENV[key_ev] or fail Dry::Credentials::KeyNotSetError
79
79
  end
80
80
  end
81
81
 
@@ -5,7 +5,7 @@ module Dry
5
5
  class Settings
6
6
 
7
7
  DEFAULT_SETTINGS = {
8
- env: -> { ENV['RACK_ENV'] },
8
+ env: -> { ENV['APP_ENV'] },
9
9
  dir: 'config/credentials',
10
10
  cipher: 'aes-256-gcm',
11
11
  digest: 'sha256',
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module Credentials
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.1"
6
6
  end
7
7
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-credentials
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Schwyn
@@ -10,27 +10,39 @@ bindir: bin
10
10
  cert_chain:
11
11
  - |
12
12
  -----BEGIN CERTIFICATE-----
13
- MIIDODCCAiCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhydWJ5
14
- L0RDPWJpdGNldGVyYS9EQz1jb20wHhcNMjIxMTA2MTIzNjUwWhcNMjMxMTA2MTIz
15
- NjUwWjAjMSEwHwYDVQQDDBhydWJ5L0RDPWJpdGNldGVyYS9EQz1jb20wggEiMA0G
13
+ MIIC+jCCAeKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhydWJ5
14
+ L0RDPWJpdGNldGVyYS9EQz1jb20wHhcNMjMxMTEwMTgyMzM2WhcNMjQxMTA5MTgy
15
+ MzM2WjAjMSEwHwYDVQQDDBhydWJ5L0RDPWJpdGNldGVyYS9EQz1jb20wggEiMA0G
16
16
  CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDcLg+IHjXYaUlTSU7R235lQKD8ZhEe
17
17
  KMhoGlSUonZ/zo1OT3KXcqTCP1iMX743xYs6upEGALCWWwq+nxvlDdnWRjF3AAv7
18
18
  ikC+Z2BEowjyeCCT/0gvn4ohKcR0JOzzRaIlFUVInlGSAHx2QHZ2N8ntf54lu7nd
19
19
  L8CiDK8rClsY4JBNGOgH9UC81f+m61UUQuTLxyM2CXfAYkj/sGNTvFRJcNX+nfdC
20
20
  hM9r2kH1+7wsa8yG7wJ2IkrzNACD8v84oE6qVusN8OLEMUI/NaEPVPbw2LUM149H
21
21
  PVa0i729A4IhroNnFNmw4wOC93ARNbM1+LW36PLMmKjKudf5Exg8VmDVAgMBAAGj
22
- dzB1MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBSfK8MtR62mQ6oN
23
- yoX/VKJzFjLSVDAdBgNVHREEFjAUgRJydWJ5QGJpdGNldGVyYS5jb20wHQYDVR0S
24
- BBYwFIEScnVieUBiaXRjZXRlcmEuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAYG2na
25
- ye8OE2DANQIFM/xDos/E4DaPWCJjX5xvFKNKHMCeQYPeZvLICCwyw2paE7Otwk6p
26
- uvbg2Ks5ykXsbk5i6vxDoeeOLvmxCqI6m+tHb8v7VZtmwRJm8so0eSX0WvTaKnIf
27
- CAn1bVUggczVdNoBXw9WAILKyw9bvh3Ft740XZrR74sd+m2pGwjCaM8hzLvrVbGP
28
- DyYhlBeRWyQKQ0WDIsiTSRhzK8HwSTUWjvPwx7SEdIU/HZgyrk0ETObKPakVu6bH
29
- kAyiRqgxF4dJviwtqI7mZIomWL63+kXLgjOjMe1SHxfIPo/0ji6+r1p4KYa7o41v
30
- fwIwU1MKlFBdsjkd
22
+ OTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBSfK8MtR62mQ6oN
23
+ yoX/VKJzFjLSVDANBgkqhkiG9w0BAQsFAAOCAQEAXhT/LpMArF3JRcZSRkJDY+dU
24
+ GKCRqOefi2iydqh1yIqXyTA9PGR1w5O6O+WS1FvF+sHCwh8fFjCuStg2L8V2RSeo
25
+ aDtfZ5s80sL8wRFxg3kek69cBuI6ozU+rf9DaXlMES4i8+zASsdv9Y4a2BsbhEdE
26
+ 9AtuMcWn5a45TOO0S4Q8OuV0v705V38Ow15J2RDRvkFRySt+//8/Vd57XAJxPXU0
27
+ k/QvZU05f6HMYBrPogJgIzHC/C5N/yeE4BVEuBDn+10Zb1iu3aDk8sd0uMgukCY8
28
+ TUmlP5A6NeGdeDJIoLgromAKs+nvI7TWzhQq9ODs51XhxgUFRCvBqUTpjTQigw==
31
29
  -----END CERTIFICATE-----
32
- date: 2023-02-22 00:00:00.000000000 Z
30
+ date: 2024-03-06 00:00:00.000000000 Z
33
31
  dependencies:
32
+ - !ruby/object:Gem::Dependency
33
+ name: base64
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
34
46
  - !ruby/object:Gem::Dependency
35
47
  name: debug
36
48
  requirement: !ruby/object:Gem::Requirement
@@ -74,7 +86,7 @@ dependencies:
74
86
  - !ruby/object:Gem::Version
75
87
  version: '0'
76
88
  - !ruby/object:Gem::Dependency
77
- name: minitest-sound
89
+ name: minitest-flash
78
90
  requirement: !ruby/object:Gem::Requirement
79
91
  requirements:
80
92
  - - ">="
@@ -203,7 +215,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
203
215
  - !ruby/object:Gem::Version
204
216
  version: '0'
205
217
  requirements: []
206
- rubygems_version: 3.4.7
218
+ rubygems_version: 3.5.6
207
219
  signing_key:
208
220
  specification_version: 4
209
221
  summary: A mixin to use encrypted credentials in your classes
metadata.gz.sig CHANGED
Binary file