kms_encrypted 0.2.0 → 0.3.0

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
- SHA1:
3
- metadata.gz: 81d18dbb1dbaa5711a97eef2a4ece83fa44e2c2c
4
- data.tar.gz: f7f8beede414a85f9ca399d8f499ff0f48b2bf53
2
+ SHA256:
3
+ metadata.gz: bec6f5799315832f67c01e6ffe79b58a5c5019e3ca8efa5a2c0795149a2b727c
4
+ data.tar.gz: 8cbea27c197f2da4dbd1cad3bbee59c36c2404cd8eddb379c07be7ebbfc72a05
5
5
  SHA512:
6
- metadata.gz: 3db72cc8666e461acedc3842829cc17e7ab0d32820eb9729e8fc1682dded9bed440f6d1201a28dacf5fdbcd350c6eaaa0035fe13274b37102a749cc8dd2c1a9f
7
- data.tar.gz: 2964f2377c884927a6dc31ea1d48aa24d3e9ac6f4b70924da5fec5d05176bb98c715357f1d4032001b6f49485c63ddcf1550e4b5d271b05507e295bc6f52b9a4
6
+ metadata.gz: ba30a61a50245321fcca825ce5d36a4f350edb26b832f033d029b6051578a1f6d3ac8efb81f01339762a1a6869f6e70ca4a4fc780d3341ea80b4d17ca57724e8
7
+ data.tar.gz: e511299714c5d035f000d7e85de6855be813fccc0a65fe7332a9418e0dc2733c6618ae42070f582877b48db98117aabc9b605ab1931537885180b20693fb7da0
data/.travis.yml CHANGED
@@ -5,8 +5,6 @@ gemfile:
5
5
  sudo: false
6
6
  before_install: gem install bundler
7
7
  script: bundle exec rake test
8
- env:
9
- - KMS_KEY_ID=insecure-test-key
10
8
  notifications:
11
9
  email:
12
10
  on_success: never
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.3.0
2
+
3
+ - Added support for Vault
4
+ - Removed `KmsEncrypted.kms_client` and `KmsEncrypted.client_options` in favor of `KmsEncrypted.aws_client`
5
+ - Removed `KmsEncrypted::Google.kms_client` in favor of `KmsEncrypted.google_client`
6
+
1
7
  ## 0.2.0
2
8
 
3
9
  - Added support for Google KMS
data/README.md CHANGED
@@ -11,7 +11,7 @@ The attr_encrypted gem is great for encryption, but:
11
11
 
12
12
  Key management services address all of these issues and it’s easy to use them together.
13
13
 
14
- Supports [Amazon KMS](https://aws.amazon.com/kms/) and [Google KMS](https://cloud.google.com/kms/)
14
+ Supports [Amazon KMS](https://aws.amazon.com/kms/), [Google KMS](https://cloud.google.com/kms/), and [Vault](https://www.vaultproject.io/)
15
15
 
16
16
  [![Build Status](https://travis-ci.org/ankane/kms_encrypted.svg?branch=master)](https://travis-ci.org/ankane/kms_encrypted)
17
17
 
@@ -29,6 +29,11 @@ Follow the instructions for your key management service:
29
29
 
30
30
  - [Amazon KMS](guides/Amazon.md)
31
31
  - [Google KMS](guides/Google.md)
32
+ - [Vault](guides/Vault.md)
33
+
34
+ ## Related Projects
35
+
36
+ To securely search encrypted data, check out [Blind Index](https://github.com/ankane/blind_index).
32
37
 
33
38
  ## History
34
39
 
data/guides/Amazon.md CHANGED
@@ -57,7 +57,7 @@ class User < ApplicationRecord
57
57
  end
58
58
  ```
59
59
 
60
- The context is used as part of the encryption and decryption process, so it must be a value that doesn’t change. Otherwise, you won’t be able to decrypt. Read more about [encryption context here](http://docs.aws.amazon.com/kms/latest/developerguide/encryption-context.html).
60
+ The context is used as part of the encryption and decryption process, so it must be a value that doesn’t change. Otherwise, you won’t be able to decrypt. Read more about [encryption context here](https://docs.aws.amazon.com/kms/latest/developerguide/encryption-context.html).
61
61
 
62
62
  The primary key is a good choice, but auto-generated ids aren’t available until a record is created, and we need to encrypt before this. One solution is to preload the primary key. Here’s what it looks like with Postgres:
63
63
 
@@ -147,7 +147,7 @@ We recommend setting up alerts on suspicious behavior.
147
147
 
148
148
  ## Key Rotation
149
149
 
150
- KMS supports [automatic key rotation](http://docs.aws.amazon.com/kms/latest/developerguide/rotate-keys.html). No action is required in this case.
150
+ KMS supports [automatic key rotation](https://docs.aws.amazon.com/kms/latest/developerguide/rotate-keys.html). No action is required in this case.
151
151
 
152
152
  To manually rotate keys, replace the old KMS key id with the new key id in your model. Your app does not need the old key id to perform rotation (however, the key must still be enabled in your AWS account).
153
153
 
@@ -259,4 +259,4 @@ user.rotate_kms_key_phone!
259
259
 
260
260
  ## File Uploads
261
261
 
262
- While outside the scope of this gem, you can also use KMS for sensitive file uploads. Check out [this guide](https://github.com/ankane/shorts/blob/master/AWS-Client-Side-Encryption.md) to learn more.
262
+ While outside the scope of this gem, you can also use KMS for sensitive file uploads. Check out [this guide](https://ankane.org/aws-client-side-encryption) to learn more.
data/guides/Google.md CHANGED
@@ -4,7 +4,7 @@ Add this line to your application’s Gemfile:
4
4
 
5
5
  ```ruby
6
6
  gem 'google-api-client'
7
- gem 'kms_encrypted', github: 'ankane/kms_encrypted'
7
+ gem 'kms_encrypted'
8
8
  ```
9
9
 
10
10
  Add columns for the encrypted data and the encrypted KMS data keys
data/guides/Vault.md ADDED
@@ -0,0 +1,143 @@
1
+ # Vault
2
+
3
+ Add this line to your application’s Gemfile:
4
+
5
+ ```ruby
6
+ gem 'vault'
7
+ gem 'kms_encrypted'
8
+ ```
9
+
10
+ Add columns for the encrypted data and the encrypted KMS data keys
11
+
12
+ ```ruby
13
+ add_column :users, :encrypted_email, :text
14
+ add_column :users, :encrypted_email_iv, :text
15
+ add_column :users, :encrypted_kms_key, :text
16
+ ```
17
+
18
+ Enable the [transit](https://www.vaultproject.io/docs/secrets/transit/index.html) backend
19
+
20
+ ```sh
21
+ vault secrets enable transit
22
+ ```
23
+
24
+ And create a key
25
+
26
+ ```sh
27
+ vault write -f transit/keys/my-key
28
+ ```
29
+
30
+ Set it in your environment along with your Vault credentials ([dotenv](https://github.com/bkeepers/dotenv) is great for this)
31
+
32
+ ```sh
33
+ KMS_KEY_ID=vault/my-key
34
+ VAULT_ADDR=http://127.0.0.1:8200
35
+ VAULT_TOKEN=secret
36
+ ```
37
+
38
+ And update your model
39
+
40
+ ```ruby
41
+ class User < ApplicationRecord
42
+ has_kms_key
43
+
44
+ attr_encrypted :email, key: :kms_key
45
+ end
46
+ ```
47
+
48
+ For each encrypted attribute, use the `kms_key` method for its key.
49
+
50
+ ## Auditing
51
+
52
+ Follow the [instructions here](https://www.vaultproject.io/docs/audit/) to set up data access logging. To know what data is being decrypted, you’ll need to add context.
53
+
54
+ Add a `kms_encryption_context` method to your model.
55
+
56
+ ```ruby
57
+ class User < ApplicationRecord
58
+ def kms_encryption_context
59
+ # some hash
60
+ end
61
+ end
62
+ ```
63
+
64
+ The context is used as part of the encryption and decryption process, so it must be a value that doesn’t change. Otherwise, you won’t be able to decrypt. Read more about [encryption context here](https://docs.aws.amazon.com/kms/latest/developerguide/encryption-context.html).
65
+
66
+ The primary key is a good choice, but auto-generated ids aren’t available until a record is created, and we need to encrypt before this. One solution is to preload the primary key. Here’s what it looks like with Postgres:
67
+
68
+ ```ruby
69
+ class User < ApplicationRecord
70
+ def kms_encryption_context
71
+ self.id ||= self.class.connection.execute("select nextval('#{self.class.sequence_name}')").first["nextval"]
72
+ {"Record" => "#{model_name}/#{id}"}
73
+ end
74
+ end
75
+ ```
76
+
77
+ ## Alerting
78
+
79
+ We recommend setting up alerts on suspicious behavior.
80
+
81
+ ## Key Rotation
82
+
83
+ To manually rotate keys, replace the old key id with the new key id in your model.
84
+
85
+ ```sh
86
+ KMS_KEY_ID=...
87
+ ```
88
+
89
+ and run
90
+
91
+ ```ruby
92
+ User.find_each do |user|
93
+ user.rotate_kms_key!
94
+ end
95
+ ```
96
+
97
+ ## Testing
98
+
99
+ For testing, you can prevent network calls to KMS by setting:
100
+
101
+ ```sh
102
+ KMS_KEY_ID=insecure-test-key
103
+ ```
104
+
105
+ ## Multiple Keys Per Record
106
+
107
+ You may want to protect different columns with different data keys (or even master keys).
108
+
109
+ To do this, add more columns
110
+
111
+ ```ruby
112
+ add_column :users, :encrypted_phone, :text
113
+ add_column :users, :encrypted_phone_iv, :text
114
+ add_column :users, :encrypted_kms_key_phone, :text
115
+ ```
116
+
117
+ And update your model
118
+
119
+ ```ruby
120
+ class User < ApplicationRecord
121
+ has_kms_key
122
+ has_kms_key name: :phone, key_id: "..."
123
+
124
+ attr_encrypted :email, key: :kms_key
125
+ attr_encrypted :phone, key: :kms_key_phone
126
+ end
127
+ ```
128
+
129
+ For context, use:
130
+
131
+ ```ruby
132
+ class User < ApplicationRecord
133
+ def kms_encryption_context_phone
134
+ # some hash
135
+ end
136
+ end
137
+ ```
138
+
139
+ To rotate keys, use:
140
+
141
+ ```ruby
142
+ user.rotate_kms_key_phone!
143
+ ```
@@ -30,4 +30,5 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency "attr_encrypted"
31
31
  spec.add_development_dependency "aws-sdk-kms"
32
32
  spec.add_development_dependency "google-api-client"
33
+ spec.add_development_dependency "vault"
33
34
  end
@@ -53,22 +53,36 @@ module KmsEncrypted
53
53
  plaintext_key = OpenSSL::Random.random_bytes(32)
54
54
 
55
55
  # encrypt it
56
+ # load client first to ensure namespace is loaded
57
+ client = KmsEncrypted.google_client
56
58
  request = ::Google::Apis::CloudkmsV1::EncryptRequest.new(
57
59
  plaintext: plaintext_key,
58
60
  additional_authenticated_data: context.to_json
59
61
  )
60
- response = KmsEncrypted::Google.kms_client.encrypt_crypto_key(key_id, request)
62
+ response = client.encrypt_crypto_key(key_id, request)
61
63
  key_version = response.name
62
64
 
63
65
  # shorten key to save space
64
- short_key_id = Base64.encode64(key_version.split("/").select.with_index { |p, i| i.odd? }.join("/"))
66
+ short_key_id = Base64.encode64(key_version.split("/").select.with_index { |_, i| i.odd? }.join("/"))
65
67
 
66
68
  # build encrypted key
67
69
  # we reference the key in the field for easy rotation
68
70
  encrypted_key = "$gc$#{short_key_id}$#{[response.ciphertext].pack(default_encoding)}"
71
+ elsif key_id.start_with?("vault/")
72
+ # generate random AES-256 key
73
+ plaintext_key = OpenSSL::Random.random_bytes(32)
74
+
75
+ # encrypt it
76
+ response = KmsEncrypted.vault_client.logical.write(
77
+ "transit/encrypt/#{key_id.sub("vault/", "")}",
78
+ plaintext: Base64.encode64(plaintext_key),
79
+ context: Base64.encode64(context.to_json)
80
+ )
81
+
82
+ encrypted_key = response.data[:ciphertext]
69
83
  else
70
84
  # generate data key from API
71
- resp = KmsEncrypted.kms_client.generate_data_key(
85
+ resp = KmsEncrypted.aws_client.generate_data_key(
72
86
  key_id: key_id,
73
87
  encryption_context: context,
74
88
  key_spec: "AES_256"
@@ -92,7 +106,7 @@ module KmsEncrypted
92
106
  }
93
107
  ActiveSupport::Notifications.instrument("decrypt_data_key.kms_encrypted", event) do
94
108
  if encrypted_key.start_with?("insecure-data-key-")
95
- plaintext_key = "00000000000000000000000000000000"
109
+ plaintext_key = "00000000000000000000000000000000".encode("BINARY")
96
110
  elsif encrypted_key.start_with?("$gc$")
97
111
  _, _, short_key_id, ciphertext = encrypted_key.split("$", 4)
98
112
 
@@ -104,13 +118,23 @@ module KmsEncrypted
104
118
  stored_key_id.insert(6, "cryptoKeys")
105
119
  stored_key_id = stored_key_id.join("/")
106
120
 
121
+ # load client first to ensure namespace is loaded
122
+ client = KmsEncrypted.google_client
107
123
  request = ::Google::Apis::CloudkmsV1::DecryptRequest.new(
108
124
  ciphertext: ciphertext.unpack(default_encoding).first,
109
125
  additional_authenticated_data: context.to_json
110
126
  )
111
- plaintext_key = KmsEncrypted::Google.kms_client.decrypt_crypto_key(stored_key_id, request).plaintext
127
+ plaintext_key = client.decrypt_crypto_key(stored_key_id, request).plaintext
128
+ elsif encrypted_key.start_with?("vault:")
129
+ response = KmsEncrypted.vault_client.logical.write(
130
+ "transit/decrypt/#{key_id.sub("vault/", "")}",
131
+ ciphertext: encrypted_key,
132
+ context: Base64.encode64(context.to_json)
133
+ )
134
+
135
+ plaintext_key = Base64.decode64(response.data[:plaintext])
112
136
  else
113
- plaintext_key = KmsEncrypted.kms_client.decrypt(
137
+ plaintext_key = KmsEncrypted.aws_client.decrypt(
114
138
  ciphertext_blob: encrypted_key.unpack(default_encoding).first,
115
139
  encryption_context: context
116
140
  ).plaintext
@@ -1,3 +1,3 @@
1
1
  module KmsEncrypted
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/kms_encrypted.rb CHANGED
@@ -1,24 +1,6 @@
1
1
  # dependencies
2
2
  require "active_support"
3
3
 
4
- begin
5
- # aws-sdk v3
6
- require "aws-sdk-kms"
7
- rescue LoadError
8
- begin
9
- # aws-sdk v2
10
- require "aws-sdk"
11
- rescue LoadError
12
- # do nothing
13
- end
14
- end
15
-
16
- begin
17
- require "google/apis/cloudkms_v1"
18
- rescue LoadError
19
- # do nothing
20
- end
21
-
22
4
  # modules
23
5
  require "kms_encrypted/log_subscriber"
24
6
  require "kms_encrypted/model"
@@ -26,43 +8,31 @@ require "kms_encrypted/version"
26
8
 
27
9
  module KmsEncrypted
28
10
  class << self
29
- attr_writer :kms_client
30
-
31
- def kms_client
32
- @kms_client ||= Aws::KMS::Client.new(client_options)
11
+ attr_writer :aws_client
12
+ attr_writer :google_client
13
+ attr_writer :vault_client
14
+
15
+ def aws_client
16
+ @aws_client ||= Aws::KMS::Client.new(
17
+ retry_limit: 2,
18
+ http_open_timeout: 2,
19
+ http_read_timeout: 2
20
+ )
33
21
  end
34
- alias_method :kms, :kms_client
35
22
 
36
- # deprecated, use kms_client instead
37
- attr_reader :client_options
38
-
39
- # deprecated, use kms_client instead
40
- def client_options=(value)
41
- @client_options = value
42
- @kms_client = nil
23
+ def google_client
24
+ @google_client ||= begin
25
+ require "google/apis/cloudkms_v1"
26
+ client = ::Google::Apis::CloudkmsV1::CloudKMSService.new
27
+ client.authorization = ::Google::Auth.get_application_default(
28
+ "https://www.googleapis.com/auth/cloud-platform"
29
+ )
30
+ client
31
+ end
43
32
  end
44
- end
45
33
 
46
- # deprecated, use kms_client instead
47
- self.client_options = {
48
- retry_limit: 2,
49
- http_open_timeout: 2,
50
- http_read_timeout: 2
51
- }
52
-
53
- module Google
54
- class << self
55
- attr_writer :kms_client
56
-
57
- def kms_client
58
- @kms_client ||= begin
59
- client = ::Google::Apis::CloudkmsV1::CloudKMSService.new
60
- client.authorization = ::Google::Auth.get_application_default(
61
- "https://www.googleapis.com/auth/cloud-platform"
62
- )
63
- client
64
- end
65
- end
34
+ def vault_client
35
+ @vault_client ||= ::Vault
66
36
  end
67
37
  end
68
38
  end
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-23 00:00:00.000000000 Z
11
+ date: 2018-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: vault
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  description:
140
154
  email:
141
155
  - andrew@chartkick.com
@@ -152,6 +166,7 @@ files:
152
166
  - Rakefile
153
167
  - guides/Amazon.md
154
168
  - guides/Google.md
169
+ - guides/Vault.md
155
170
  - kms_encrypted.gemspec
156
171
  - lib/kms_encrypted.rb
157
172
  - lib/kms_encrypted/log_subscriber.rb
@@ -177,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
192
  version: '0'
178
193
  requirements: []
179
194
  rubyforge_project:
180
- rubygems_version: 2.6.13
195
+ rubygems_version: 2.7.7
181
196
  signing_key:
182
197
  specification_version: 4
183
198
  summary: Simple, secure key management for attr_encrypted