active_kms 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4ef69ec3793d206e0f6dc11c39de7d61af017d4c743441c20592411fab4ed54c
4
+ data.tar.gz: 7ddc9779bf39ce4dbbcbb52bb4f58a33407916cb58865de66aa761b4dd0da12a
5
+ SHA512:
6
+ metadata.gz: 8285f7d2a15d25507104264c66c29dfbc6fe745d0161a860d8cd9115f75b1748d574cdcf3e5a07557397f95fd51188bb7795eb2742c442cf37f6837502ef872e
7
+ data.tar.gz: 4240335bc026f1f36bdacdeb781b205cb4f809f81694bb83a3d69e10ee07e360ca50be999da68aa3236427b579f87c40998d7d8395d0d2438664fc15e300db1a
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2021-12-14)
2
+
3
+ - First release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Andrew Kane
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # Active KMS
2
+
3
+ Simple, secure key management for [Active Record encryption](https://edgeguides.rubyonrails.org/active_record_encryption.html)
4
+
5
+ **Note:** This project is experimental until Rails 7 is released. At the moment, encryption requires three encryption requests and one decryption request. See [this Rails issue](https://github.com/rails/rails/issues/42388) for more info. As a result, there’s no way to grant encryption and decryption permission separately.
6
+
7
+ [![Build Status](https://github.com/ankane/active_kms/workflows/build/badge.svg?branch=master)](https://github.com/ankane/active_kms/actions)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application’s Gemfile:
12
+
13
+ ```ruby
14
+ gem "active_kms"
15
+ ```
16
+
17
+ And follow the instructions for your key management service:
18
+
19
+ - [AWS KMS](#aws-kms)
20
+ - [Google Cloud KMS](#google-cloud-kms)
21
+ - [Vault](#vault)
22
+
23
+ ### AWS KMS
24
+
25
+ Add this line to your application’s Gemfile:
26
+
27
+ ```ruby
28
+ gem "aws-sdk-kms"
29
+ ```
30
+
31
+ 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.
32
+
33
+ Create a [KMS master key](https://console.aws.amazon.com/kms/home#/kms/keys) and set it in your environment along with your AWS credentials ([dotenv](https://github.com/bkeepers/dotenv) is great for this)
34
+
35
+ ```sh
36
+ KMS_KEY_ID=alias/my-key
37
+ AWS_ACCESS_KEY_ID=...
38
+ AWS_SECRET_ACCESS_KEY=...
39
+ ```
40
+
41
+ And add to `config/application.rb`:
42
+
43
+ ```ruby
44
+ config.active_record.encryption.key_provider = ActiveKms::AwsKeyProvider.new(key_id: ENV["KMS_KEY_ID"])
45
+ ```
46
+
47
+ ### Google Cloud KMS
48
+
49
+ Add this line to your application’s Gemfile:
50
+
51
+ ```ruby
52
+ gem "google-cloud-kms"
53
+ ```
54
+
55
+ Create a [Google Cloud Platform](https://cloud.google.com/) account if you don’t have one. KMS works great whether or not you run your infrastructure on GCP.
56
+
57
+ Create a [KMS key ring and key](https://console.cloud.google.com/iam-admin/kms) and set it in your environment along with your GCP credentials ([dotenv](https://github.com/bkeepers/dotenv) is great for this)
58
+
59
+ ```sh
60
+ KMS_KEY_ID=projects/my-project/locations/global/keyRings/my-key-ring/cryptoKeys/my-key
61
+ ```
62
+
63
+ And add to `config/application.rb`:
64
+
65
+ ```ruby
66
+ config.active_record.encryption.key_provider = ActiveKms::GoogleCloudKeyProvider.new(key_id: ENV["KMS_KEY_ID"])
67
+ ```
68
+
69
+ ### Vault
70
+
71
+ Add this line to your application’s Gemfile:
72
+
73
+ ```ruby
74
+ gem "vault"
75
+ ```
76
+
77
+ Enable the [transit](https://www.vaultproject.io/docs/secrets/transit/index.html) secrets engine
78
+
79
+ ```sh
80
+ vault secrets enable transit
81
+ ```
82
+
83
+ And create a key
84
+
85
+ ```sh
86
+ vault write -f transit/keys/my-key
87
+ ```
88
+
89
+ Set it in your environment along with your Vault credentials ([dotenv](https://github.com/bkeepers/dotenv) is great for this)
90
+
91
+ ```sh
92
+ KMS_KEY_ID=my-key
93
+ VAULT_ADDR=http://127.0.0.1:8200
94
+ VAULT_TOKEN=secret
95
+ ```
96
+
97
+ And add to `config/application.rb`:
98
+
99
+ ```ruby
100
+ config.active_record.encryption.key_provider = ActiveKms::VaultKeyProvider.new(key_id: ENV["KMS_KEY_ID"])
101
+ ```
102
+
103
+ ## Per-Attribute Keys
104
+
105
+ Specify per-attribute keys
106
+
107
+ ```ruby
108
+ class User < ApplicationRecord
109
+ encrypts :email, key_provider: ActiveKms::AwsKeyProvider.new(key_id: "...")
110
+ end
111
+ ```
112
+
113
+ ## Testing
114
+
115
+ For testing, you can prevent network calls to KMS by adding to `config/environments/test.rb`:
116
+
117
+ ```ruby
118
+ config.active_record.encryption.key_provider = ActiveKms::TestKeyProvider.new
119
+ ```
120
+
121
+ ## Key Rotation
122
+
123
+ Key management services allow you to rotate the master key without any code changes.
124
+
125
+ - For AWS KMS, you can use [automatic key rotation](https://docs.aws.amazon.com/kms/latest/developerguide/rotate-keys.html)
126
+ - For Google Cloud, use the Google Cloud Console or API
127
+ - For Vault, use:
128
+
129
+ ```sh
130
+ vault write -f transit/keys/my-key/rotate
131
+ ```
132
+
133
+ New data will be encrypted with the new master key version.
134
+
135
+ ### Switching Keys
136
+
137
+ You can change keys within your current KMS or move to a different KMS without downtime.
138
+
139
+ Set globally in `config/application.rb`:
140
+
141
+ ```ruby
142
+ config.active_record.encryption.previous = [{key_provider: ActiveKms::AwsKeyProvider.new(key_id: "...")}]
143
+ ```
144
+
145
+ Or per-attribute:
146
+
147
+ ```ruby
148
+ class User < ApplicationRecord
149
+ encrypts :email, previous: [{key_provider: ActiveKms::AwsKeyProvider.new(key_id: "...")}]
150
+ end
151
+ ```
152
+
153
+ ## Reference
154
+
155
+ Specify a client
156
+
157
+ ```ruby
158
+ ActiveKms::AwsKeyProvider.new(client: Aws::KMS::Client.new, ...)
159
+ # or
160
+ ActiveKms::GoogleCloudKeyProvider.new(client: Google::Cloud::Kms.key_management_service, ...)
161
+ # or
162
+ ActiveKms::VaultKeyProvider.new(client: Vault::Client.new, ...)
163
+ ```
164
+
165
+ ## History
166
+
167
+ View the [changelog](https://github.com/ankane/active_kms/blob/master/CHANGELOG.md)
168
+
169
+ ## Contributing
170
+
171
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
172
+
173
+ - [Report bugs](https://github.com/ankane/active_kms/issues)
174
+ - Fix bugs and [submit pull requests](https://github.com/ankane/active_kms/pulls)
175
+ - Write, clarify, or fix documentation
176
+ - Suggest or add new features
177
+
178
+ To get started with development:
179
+
180
+ ```sh
181
+ git clone https://github.com/ankane/active_kms.git
182
+ cd active_kms
183
+ bundle install
184
+ bundle exec rake test
185
+ ```
@@ -0,0 +1,28 @@
1
+ module ActiveKms
2
+ class AwsKeyProvider < BaseKeyProvider
3
+ private
4
+
5
+ def default_client
6
+ Aws::KMS::Client.new(
7
+ retry_limit: 1,
8
+ http_open_timeout: 2,
9
+ http_read_timeout: 2
10
+ )
11
+ end
12
+
13
+ def encrypt(key_id, data_key)
14
+ client.encrypt(key_id: key_id, plaintext: data_key).ciphertext_blob
15
+ end
16
+
17
+ def decrypt(_, encrypted_data_key)
18
+ client.decrypt(ciphertext_blob: encrypted_data_key).plaintext
19
+ end
20
+
21
+ # key is stored in ciphertext so don't need to store reference
22
+ # reference could be useful for multiple AWS clients
23
+ # so consider an option in the future
24
+ def key_id_header
25
+ "aws"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ module ActiveKms
2
+ class BaseKeyProvider
3
+ attr_reader :key_id, :client
4
+
5
+ def initialize(key_id:, client: nil)
6
+ @key_id = key_id
7
+ @client = client || default_client
8
+ end
9
+
10
+ def encryption_key
11
+ data_key = ActiveRecord::Encryption.key_generator.generate_random_key
12
+ encrypted_data_key =
13
+ ActiveSupport::Notifications.instrument("encrypt.active_kms") do
14
+ encrypt(key_id, data_key)
15
+ end
16
+
17
+ key = ActiveRecord::Encryption::Key.new(data_key)
18
+ key.public_tags.encrypted_data_key = encrypted_data_key
19
+ key.public_tags.encrypted_data_key_id = key_id_header
20
+ key
21
+ end
22
+
23
+ def decryption_keys(encrypted_message)
24
+ return [] if encrypted_message.headers.encrypted_data_key_id != key_id_header
25
+
26
+ encrypted_data_key = encrypted_message.headers.encrypted_data_key
27
+ # rescue errors to try previous keys
28
+ # rescue outside Active Support notification for more intuitive output
29
+ begin
30
+ data_key =
31
+ ActiveSupport::Notifications.instrument("decrypt.active_kms") do
32
+ decrypt(key_id, encrypted_data_key)
33
+ end
34
+ [ActiveRecord::Encryption::Key.new(data_key)]
35
+ rescue => e
36
+ warn "[active_kms] #{e.class.name}: #{e.message}"
37
+ []
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def key_id_header
44
+ @key_id_header ||= "#{prefix}/#{Digest::SHA1.hexdigest(key_id).first(4)}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveKms
2
+ class GoogleCloudKeyProvider < BaseKeyProvider
3
+ private
4
+
5
+ def default_client
6
+ require "google/cloud/kms"
7
+
8
+ Google::Cloud::Kms.key_management_service do |config|
9
+ config.timeout = 2
10
+ end
11
+ end
12
+
13
+ def encrypt(key_id, data_key)
14
+ client.encrypt(name: key_id, plaintext: data_key).ciphertext
15
+ end
16
+
17
+ def decrypt(key_id, encrypted_data_key)
18
+ client.decrypt(name: key_id, ciphertext: encrypted_data_key).plaintext
19
+ end
20
+
21
+ def prefix
22
+ "gc"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveKms
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+ def decrypt(event)
4
+ return unless logger.debug?
5
+
6
+ name = "Decrypt Data Key (#{event.duration.round(1)}ms)"
7
+ debug " #{color(name, YELLOW, true)}"
8
+ end
9
+
10
+ def encrypt(event)
11
+ return unless logger.debug?
12
+
13
+ name = "Encrypt Data Key (#{event.duration.round(1)}ms)"
14
+ debug " #{color(name, YELLOW, true)}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module ActiveKms
2
+ class TestKeyProvider < BaseKeyProvider
3
+ def initialize
4
+ end
5
+
6
+ private
7
+
8
+ def encrypt(_, data_key)
9
+ data_key
10
+ end
11
+
12
+ def decrypt(_, encrypted_data_key)
13
+ encrypted_data_key
14
+ end
15
+
16
+ def key_id_header
17
+ "test"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module ActiveKms
2
+ class VaultKeyProvider < BaseKeyProvider
3
+ private
4
+
5
+ def default_client
6
+ Vault::Client.new
7
+ end
8
+
9
+ def encrypt(key_id, data_key)
10
+ client.logical.write("transit/encrypt/#{key_id}", plaintext: Base64.encode64(data_key)).data[:ciphertext]
11
+ end
12
+
13
+ def decrypt(key_id, encrypted_data_key)
14
+ Base64.decode64(client.logical.write("transit/decrypt/#{key_id}", ciphertext: encrypted_data_key).data[:plaintext])
15
+ end
16
+
17
+ # could store entire key_id in key_id_header but prefer reference
18
+ def prefix
19
+ "vt"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveKms
2
+ VERSION = "0.1.0"
3
+ end
data/lib/active_kms.rb ADDED
@@ -0,0 +1,19 @@
1
+ # dependencies
2
+ require "active_support"
3
+
4
+ # modules
5
+ require "active_kms/base_key_provider"
6
+ require "active_kms/log_subscriber"
7
+ require "active_kms/version"
8
+
9
+ # providers
10
+ require "active_kms/aws_key_provider"
11
+ require "active_kms/google_cloud_key_provider"
12
+ require "active_kms/test_key_provider"
13
+ require "active_kms/vault_key_provider"
14
+
15
+ module ActiveKms
16
+ class Error < StandardError; end
17
+ end
18
+
19
+ ActiveKms::LogSubscriber.attach_to :active_kms
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_kms
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-12-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.0.rc3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.0.rc3
27
+ description:
28
+ email: andrew@ankane.org
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - CHANGELOG.md
34
+ - LICENSE.txt
35
+ - README.md
36
+ - lib/active_kms.rb
37
+ - lib/active_kms/aws_key_provider.rb
38
+ - lib/active_kms/base_key_provider.rb
39
+ - lib/active_kms/google_cloud_key_provider.rb
40
+ - lib/active_kms/log_subscriber.rb
41
+ - lib/active_kms/test_key_provider.rb
42
+ - lib/active_kms/vault_key_provider.rb
43
+ - lib/active_kms/version.rb
44
+ homepage: https://github.com/ankane/active_kms
45
+ licenses:
46
+ - MIT
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '2.6'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.2.32
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Simple, secure key management for Active Record encryption
67
+ test_files: []