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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 48601fd1b07d25fafe4a14a508511bd3237ef486
4
- data.tar.gz: 74add3ece85f95c4e1a2b5483b772c2075def8c4
3
+ metadata.gz: 1e8f9ae8d4c6e01a94c6daa2ae422b57ce4bcec1
4
+ data.tar.gz: fe2ad3ac82398c643ae60c5a88830c6108864c1c
5
5
  SHA512:
6
- metadata.gz: 1461f4cbdd77bb98eba915832ee402d1467f8ed5b3578295010ece703864da16418cc7925f78fc64ffc6ed1176e34c07cc5912556fea8b2f6726b872b669e56b
7
- data.tar.gz: 6268c77a1f065ce79ebf1a2cf954a7d36265d96135b54be8aeabec96d5b72828b8929f69dcc2778bbee3eda8247f1735747af22ed6a814fdf2428fa5d5510673
6
+ metadata.gz: aee2523daea0e3cc176606d845d94ed98ba8ca8a2a51beb90227c638e7d0f84e888639846d78585d446f5dae7371b41c5e98b603b50330423232c0e1e5fe1976
7
+ data.tar.gz: 841ec21de2384e5d062c6e3f69d3f4a12cabe8cb99f21f169d1bbb931191f21f9b42964dbb1d8f98015b349df931e41dfc0dcf958fc3cb736913918478de429b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.1.4
2
+
3
+ - Added `kms_keys` method to models
4
+ - Reset data keys when record is reloaded
5
+ - Added `kms_client`
6
+ - Added ActiveSupport notifications
7
+
1
8
  ## 0.1.3
2
9
 
3
10
  - Added test key
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 use other AWS services.
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.encrypted_kms_key = nil # before user.save
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
@@ -1,3 +1,3 @@
1
1
  module KmsEncrypted
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
data/lib/kms_encrypted.rb CHANGED
@@ -1,91 +1,41 @@
1
- require "kms_encrypted/version"
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
- attr_accessor :client_options
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.3
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-02 00:00:00.000000000 Z
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: []