kms_encrypted 0.1.3 → 0.1.4
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
- data/CHANGELOG.md +7 -0
- data/README.md +5 -3
- data/lib/kms_encrypted/log_subscriber.rb +17 -0
- data/lib/kms_encrypted/model.rb +114 -0
- data/lib/kms_encrypted/version.rb +1 -1
- data/lib/kms_encrypted.rb +25 -75
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e8f9ae8d4c6e01a94c6daa2ae422b57ce4bcec1
|
4
|
+
data.tar.gz: fe2ad3ac82398c643ae60c5a88830c6108864c1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aee2523daea0e3cc176606d845d94ed98ba8ca8a2a51beb90227c638e7d0f84e888639846d78585d446f5dae7371b41c5e98b603b50330423232c0e1e5fe1976
|
7
|
+
data.tar.gz: 841ec21de2384e5d062c6e3f69d3f4a12cabe8cb99f21f169d1bbb931191f21f9b42964dbb1d8f98015b349df931e41dfc0dcf958fc3cb736913918478de429b
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -37,7 +37,7 @@ add_column :users, :encrypted_email_iv, :text
|
|
37
37
|
add_column :users, :encrypted_kms_key, :text
|
38
38
|
```
|
39
39
|
|
40
|
-
Create an [Amazon Web Services](https://aws.amazon.com/) account if you don’t have one. KMS works great whether or not you
|
40
|
+
Create an [Amazon Web Services](https://aws.amazon.com/) account if you don’t have one. KMS works great whether or not you run your infrastructure on AWS.
|
41
41
|
|
42
42
|
Create a [KMS master key](https://console.aws.amazon.com/iam/home#/encryptionKeys) and set it in your environment ([dotenv](https://github.com/bkeepers/dotenv) is great for this)
|
43
43
|
|
@@ -203,10 +203,12 @@ To encrypt the data, use a policy with:
|
|
203
203
|
}
|
204
204
|
```
|
205
205
|
|
206
|
-
If a system can only encrypt, you must clear out existing data keys before updates.
|
206
|
+
If a system can only encrypt, you must clear out existing data and data keys before updates.
|
207
207
|
|
208
208
|
```ruby
|
209
|
-
user.
|
209
|
+
user.encrypted_email = nil
|
210
|
+
user.encrypted_kms_key = nil
|
211
|
+
# before user.save or user.update
|
210
212
|
```
|
211
213
|
|
212
214
|
To decrypt the data, use a policy with:
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module KmsEncrypted
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def decrypt_data_key(event)
|
4
|
+
return unless logger.debug?
|
5
|
+
|
6
|
+
name = "Decrypt Data Key (#{event.duration.round(1)}ms)"
|
7
|
+
debug " #{color(name, YELLOW, true)} Context: #{event.payload[:context].inspect}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate_data_key(event)
|
11
|
+
return unless logger.debug?
|
12
|
+
|
13
|
+
name = "Generate Data Key (#{event.duration.round(1)}ms)"
|
14
|
+
debug " #{color(name, YELLOW, true)} Context: #{event.payload[:context].inspect}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module KmsEncrypted
|
2
|
+
module Model
|
3
|
+
def has_kms_key(legacy_key_id = nil, name: nil, key_id: nil)
|
4
|
+
key_id ||= legacy_key_id || ENV["KMS_KEY_ID"]
|
5
|
+
|
6
|
+
key_method = name ? "kms_key_#{name}" : "kms_key"
|
7
|
+
|
8
|
+
class_eval do
|
9
|
+
class << self
|
10
|
+
def kms_keys
|
11
|
+
@kms_keys ||= {}
|
12
|
+
end unless respond_to?(:kms_keys)
|
13
|
+
end
|
14
|
+
kms_keys[key_method.to_sym] = {key_id: key_id}
|
15
|
+
|
16
|
+
# same pattern as attr_encrypted reload
|
17
|
+
if method_defined?(:reload) && kms_keys.size == 1
|
18
|
+
alias_method :reload_without_kms_encrypted, :reload
|
19
|
+
def reload(*args, &block)
|
20
|
+
result = reload_without_kms_encrypted(*args, &block)
|
21
|
+
self.class.kms_keys.keys.each do |key_method|
|
22
|
+
instance_variable_set("@#{key_method}", nil)
|
23
|
+
end
|
24
|
+
result
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
define_method(key_method) do
|
29
|
+
raise ArgumentError, "Missing key id" unless key_id
|
30
|
+
|
31
|
+
instance_var = "@#{key_method}"
|
32
|
+
|
33
|
+
unless instance_variable_get(instance_var)
|
34
|
+
key_column = "encrypted_#{key_method}"
|
35
|
+
context_method = name ? "kms_encryption_context_#{name}" : "kms_encryption_context"
|
36
|
+
context = respond_to?(context_method, true) ? send(context_method) : {}
|
37
|
+
default_encoding = "m"
|
38
|
+
|
39
|
+
unless send(key_column)
|
40
|
+
plaintext_key = nil
|
41
|
+
encrypted_key = nil
|
42
|
+
|
43
|
+
event = {
|
44
|
+
key_id: key_id,
|
45
|
+
context: context
|
46
|
+
}
|
47
|
+
ActiveSupport::Notifications.instrument("generate_data_key.kms_encrypted", event) do
|
48
|
+
if key_id == "insecure-test-key"
|
49
|
+
encrypted_key = "insecure-data-key-#{rand(1_000_000_000_000)}"
|
50
|
+
plaintext_key = "00000000000000000000000000000000"
|
51
|
+
else
|
52
|
+
resp = KmsEncrypted.kms_client.generate_data_key(
|
53
|
+
key_id: key_id,
|
54
|
+
encryption_context: context,
|
55
|
+
key_spec: "AES_256"
|
56
|
+
)
|
57
|
+
encrypted_key = [resp.ciphertext_blob].pack(default_encoding)
|
58
|
+
plaintext_key = resp.plaintext
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
instance_variable_set(instance_var, plaintext_key)
|
63
|
+
self.send("#{key_column}=", encrypted_key)
|
64
|
+
end
|
65
|
+
|
66
|
+
unless instance_variable_get(instance_var)
|
67
|
+
encrypted_key = send(key_column)
|
68
|
+
plaintext_key = nil
|
69
|
+
|
70
|
+
event = {
|
71
|
+
key_id: key_id,
|
72
|
+
context: context
|
73
|
+
}
|
74
|
+
ActiveSupport::Notifications.instrument("decrypt_data_key.kms_encrypted", event) do
|
75
|
+
if key_id == "insecure-test-key"
|
76
|
+
plaintext_key = "00000000000000000000000000000000"
|
77
|
+
else
|
78
|
+
plaintext_key = KmsEncrypted.kms_client.decrypt(
|
79
|
+
ciphertext_blob: encrypted_key.unpack(default_encoding).first,
|
80
|
+
encryption_context: context
|
81
|
+
).plaintext
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
instance_variable_set(instance_var, plaintext_key)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
instance_variable_get(instance_var)
|
90
|
+
end
|
91
|
+
|
92
|
+
define_method("rotate_#{key_method}!") do
|
93
|
+
# decrypt
|
94
|
+
plaintext_attributes = {}
|
95
|
+
self.class.encrypted_attributes.select { |_, v| v[:key] == key_method.to_sym }.keys.each do |key|
|
96
|
+
plaintext_attributes[key] = send(key)
|
97
|
+
end
|
98
|
+
|
99
|
+
# reset key
|
100
|
+
instance_variable_set("@#{key_method}", nil)
|
101
|
+
send("encrypted_#{key_method}=", nil)
|
102
|
+
|
103
|
+
# encrypt again
|
104
|
+
plaintext_attributes.each do |attr, value|
|
105
|
+
send("#{attr}=", value)
|
106
|
+
end
|
107
|
+
|
108
|
+
# update atomically
|
109
|
+
save!
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/kms_encrypted.rb
CHANGED
@@ -1,91 +1,41 @@
|
|
1
|
-
|
1
|
+
# dependencies
|
2
2
|
require "active_support"
|
3
3
|
require "aws-sdk-kms"
|
4
4
|
|
5
|
+
# modules
|
6
|
+
require "kms_encrypted/log_subscriber"
|
7
|
+
require "kms_encrypted/model"
|
8
|
+
require "kms_encrypted/version"
|
9
|
+
|
5
10
|
module KmsEncrypted
|
6
11
|
class << self
|
7
|
-
|
12
|
+
attr_writer :kms_client
|
13
|
+
|
14
|
+
def kms_client
|
15
|
+
@kms_client ||= Aws::KMS::Client.new(client_options)
|
16
|
+
end
|
17
|
+
alias_method :kms, :kms_client
|
18
|
+
|
19
|
+
# deprecated, use kms_client instead
|
20
|
+
attr_reader :client_options
|
21
|
+
|
22
|
+
# deprecated, use kms_client instead
|
23
|
+
def client_options=(value)
|
24
|
+
@client_options = value
|
25
|
+
@kms_client = nil
|
26
|
+
end
|
8
27
|
end
|
28
|
+
|
29
|
+
# deprecated, use kms_client instead
|
9
30
|
self.client_options = {
|
10
31
|
retry_limit: 2,
|
11
32
|
http_open_timeout: 2,
|
12
33
|
http_read_timeout: 2
|
13
34
|
}
|
14
|
-
|
15
|
-
def self.kms
|
16
|
-
@kms ||= Aws::KMS::Client.new(client_options)
|
17
|
-
end
|
18
|
-
|
19
|
-
module Model
|
20
|
-
def has_kms_key(legacy_key_id = nil, name: nil, key_id: nil)
|
21
|
-
key_id ||= legacy_key_id || ENV["KMS_KEY_ID"]
|
22
|
-
|
23
|
-
key_method = name ? "kms_key_#{name}" : "kms_key"
|
24
|
-
|
25
|
-
class_eval do
|
26
|
-
define_method(key_method) do
|
27
|
-
raise ArgumentError, "Missing key id" unless key_id
|
28
|
-
|
29
|
-
instance_var = "@#{key_method}"
|
30
|
-
|
31
|
-
unless instance_variable_get(instance_var)
|
32
|
-
if key_id == "insecure-test-key"
|
33
|
-
instance_variable_set(instance_var, "00000000000000000000000000000000")
|
34
|
-
else
|
35
|
-
key_column = "encrypted_#{key_method}"
|
36
|
-
context_method = name ? "kms_encryption_context_#{name}" : "kms_encryption_context"
|
37
|
-
context = respond_to?(context_method, true) ? send(context_method) : {}
|
38
|
-
default_encoding = "m"
|
39
|
-
|
40
|
-
unless send(key_column)
|
41
|
-
resp = KmsEncrypted.kms.generate_data_key(
|
42
|
-
key_id: key_id,
|
43
|
-
encryption_context: context,
|
44
|
-
key_spec: "AES_256"
|
45
|
-
)
|
46
|
-
ciphertext = resp.ciphertext_blob
|
47
|
-
instance_variable_set(instance_var, resp.plaintext)
|
48
|
-
self.send("#{key_column}=", [resp.ciphertext_blob].pack(default_encoding))
|
49
|
-
end
|
50
|
-
|
51
|
-
unless instance_variable_get(instance_var)
|
52
|
-
ciphertext = send(key_column).unpack(default_encoding).first
|
53
|
-
resp = KmsEncrypted.kms.decrypt(
|
54
|
-
ciphertext_blob: ciphertext,
|
55
|
-
encryption_context: context
|
56
|
-
)
|
57
|
-
instance_variable_set(instance_var, resp.plaintext)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
instance_variable_get(instance_var)
|
63
|
-
end
|
64
|
-
|
65
|
-
define_method("rotate_#{key_method}!") do
|
66
|
-
# decrypt
|
67
|
-
plaintext_attributes = {}
|
68
|
-
self.class.encrypted_attributes.select { |_, v| v[:key] == key_method.to_sym }.keys.each do |key|
|
69
|
-
plaintext_attributes[key] = send(key)
|
70
|
-
end
|
71
|
-
|
72
|
-
# reset key
|
73
|
-
instance_variable_set("@#{key_method}", nil)
|
74
|
-
send("encrypted_#{key_method}=", nil)
|
75
|
-
|
76
|
-
# encrypt again
|
77
|
-
plaintext_attributes.each do |attr, value|
|
78
|
-
send("#{attr}=", value)
|
79
|
-
end
|
80
|
-
|
81
|
-
# update atomically
|
82
|
-
save!
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
35
|
end
|
88
36
|
|
89
37
|
ActiveSupport.on_load(:active_record) do
|
90
38
|
extend KmsEncrypted::Model
|
91
39
|
end
|
40
|
+
|
41
|
+
KmsEncrypted::LogSubscriber.attach_to :kms_encrypted
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kms_encrypted
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-12-
|
11
|
+
date: 2017-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-kms
|
@@ -137,6 +137,8 @@ files:
|
|
137
137
|
- Rakefile
|
138
138
|
- kms_encrypted.gemspec
|
139
139
|
- lib/kms_encrypted.rb
|
140
|
+
- lib/kms_encrypted/log_subscriber.rb
|
141
|
+
- lib/kms_encrypted/model.rb
|
140
142
|
- lib/kms_encrypted/version.rb
|
141
143
|
homepage: https://github.com/ankane/kms_encrypted
|
142
144
|
licenses: []
|