dry-credentials 0.1.0 → 0.2.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +18 -1
- data/README.md +108 -3
- data/lib/dry/credentials/encryptor.rb +17 -19
- data/lib/dry/credentials/extension.rb +14 -4
- data/lib/dry/credentials/helpers.rb +1 -1
- data/lib/dry/credentials/settings.rb +1 -1
- data/lib/dry/credentials/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +28 -16
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d4a73f1a9d55f7e32296e39d78e7ff5a6273226c25d10e97b0d2a10b9a00f1c
|
4
|
+
data.tar.gz: 56f39f76baf2a18dbc4bf163d5d12697687a2a026fcc9d01f6b632fef7dcf633
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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["
|
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)
|
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
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
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
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
23
|
-
yoX/
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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:
|
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-
|
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.
|
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
|