kms_encrypted 0.1.3 → 0.1.4

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
  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: []