vault-rails 0.3.2 → 0.7.1

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.
data/lib/vault/rails.rb CHANGED
@@ -6,7 +6,7 @@ require "json"
6
6
  require_relative "encrypted_model"
7
7
  require_relative "rails/configurable"
8
8
  require_relative "rails/errors"
9
- require_relative "rails/serializer"
9
+ require_relative "rails/json_serializer"
10
10
  require_relative "rails/version"
11
11
 
12
12
  module Vault
@@ -26,6 +26,7 @@ module Vault
26
26
  # The warning string to print when running in development mode.
27
27
  DEV_WARNING = "[vault-rails] Using in-memory cipher - this is not secure " \
28
28
  "and should never be used in production-like environments!".freeze
29
+ DEV_PREFIX = "vault:dev:".freeze
29
30
 
30
31
  class << self
31
32
  # API client object based off the configured options in {Configurable}.
@@ -72,7 +73,7 @@ module Vault
72
73
  #
73
74
  # @return [String]
74
75
  # the encrypted cipher text
75
- def encrypt(path, key, plaintext, client = self.client)
76
+ def encrypt(path, key, plaintext, client: self.client, context: nil)
76
77
  if plaintext.blank?
77
78
  return plaintext
78
79
  end
@@ -82,9 +83,9 @@ module Vault
82
83
 
83
84
  with_retries do
84
85
  if self.enabled?
85
- result = self.vault_encrypt(path, key, plaintext, client)
86
+ result = self.vault_encrypt(path, key, plaintext, client: client, context: context)
86
87
  else
87
- result = self.memory_encrypt(path, key, plaintext, client)
88
+ result = self.memory_encrypt(path, key, plaintext, client: client, context: context)
88
89
  end
89
90
 
90
91
  return self.force_encoding(result)
@@ -104,7 +105,7 @@ module Vault
104
105
  #
105
106
  # @return [String]
106
107
  # the decrypted plaintext text
107
- def decrypt(path, key, ciphertext, client = self.client)
108
+ def decrypt(path, key, ciphertext, client: self.client, context: nil)
108
109
  if ciphertext.blank?
109
110
  return ciphertext
110
111
  end
@@ -114,9 +115,9 @@ module Vault
114
115
 
115
116
  with_retries do
116
117
  if self.enabled?
117
- result = self.vault_decrypt(path, key, ciphertext, client)
118
+ result = self.vault_decrypt(path, key, ciphertext, client: client, context: context)
118
119
  else
119
- result = self.memory_decrypt(path, key, ciphertext, client)
120
+ result = self.memory_decrypt(path, key, ciphertext, client: client, context: context)
120
121
  end
121
122
 
122
123
  return self.force_encoding(result)
@@ -140,58 +141,101 @@ module Vault
140
141
  end
141
142
  end
142
143
 
144
+ def transform_encode(plaintext, opts={})
145
+ return plaintext if plaintext&.empty?
146
+ request_opts = {}
147
+ request_opts[:value] = plaintext
148
+
149
+ if opts[:transformation]
150
+ request_opts[:transformation] = opts[:transformation]
151
+ end
152
+
153
+ role_name = transform_role_name(opts)
154
+ client.transform.encode(role_name: role_name, **request_opts)
155
+ end
156
+
157
+ def transform_decode(ciphertext, opts={})
158
+ return ciphertext if ciphertext&.empty?
159
+ request_opts = {}
160
+ request_opts[:value] = ciphertext
161
+
162
+ if opts[:transformation]
163
+ request_opts[:transformation] = opts[:transformation]
164
+ end
165
+
166
+ role_name = transform_role_name(opts)
167
+ puts request_opts
168
+ client.transform.decode(role_name: role_name, **request_opts)
169
+ end
170
+
171
+
143
172
  protected
144
173
 
145
174
  # Perform in-memory encryption. This is useful for testing and development.
146
- def memory_encrypt(path, key, plaintext, client)
175
+ def memory_encrypt(path, key, plaintext, client: , context: nil)
147
176
  log_warning(DEV_WARNING) if self.in_memory_warnings_enabled?
148
177
 
149
178
  return nil if plaintext.nil?
150
179
 
151
180
  cipher = OpenSSL::Cipher::AES.new(128, :CBC)
152
181
  cipher.encrypt
153
- cipher.key = memory_key_for(path, key)
154
- return Base64.strict_encode64(cipher.update(plaintext) + cipher.final)
182
+ cipher.key = memory_key_for(path, key, context: context)
183
+ return DEV_PREFIX + Base64.strict_encode64(cipher.update(plaintext) + cipher.final)
155
184
  end
156
185
 
157
186
  # Perform in-memory decryption. This is useful for testing and development.
158
- def memory_decrypt(path, key, ciphertext, client)
187
+ def memory_decrypt(path, key, ciphertext, client: , context: nil)
159
188
  log_warning(DEV_WARNING) if self.in_memory_warnings_enabled?
160
189
 
161
190
  return nil if ciphertext.nil?
162
191
 
192
+ raise Vault::Rails::InvalidCiphertext.new(ciphertext) if !ciphertext.start_with?(DEV_PREFIX)
193
+ data = ciphertext[DEV_PREFIX.length..-1]
194
+
163
195
  cipher = OpenSSL::Cipher::AES.new(128, :CBC)
164
196
  cipher.decrypt
165
- cipher.key = memory_key_for(path, key)
166
- return cipher.update(Base64.strict_decode64(ciphertext)) + cipher.final
197
+ cipher.key = memory_key_for(path, key, context: context)
198
+ return cipher.update(Base64.strict_decode64(data)) + cipher.final
199
+ end
200
+
201
+ # The symmetric key for the given params.
202
+ # @return [String]
203
+ def memory_key_for(path, key, context: nil)
204
+ md5 = OpenSSL::Digest::MD5.new
205
+ md5 << path
206
+ md5 << key
207
+ md5 << context if context
208
+ md5.digest
167
209
  end
168
210
 
169
211
  # Perform encryption using Vault. This will raise exceptions if Vault is
170
212
  # unavailable.
171
- def vault_encrypt(path, key, plaintext, client)
213
+ def vault_encrypt(path, key, plaintext, client: , context: nil)
172
214
  return nil if plaintext.nil?
173
215
 
174
- route = File.join(path, "encrypt", key)
175
- secret = client.logical.write(route,
176
- plaintext: Base64.strict_encode64(plaintext),
177
- )
216
+ route = File.join(path, "encrypt", key)
217
+
218
+ data = { plaintext: Base64.strict_encode64(plaintext) }
219
+ data[:context] = Base64.strict_encode64(context) if context
220
+
221
+ secret = client.logical.write(route, data)
222
+
178
223
  return secret.data[:ciphertext]
179
224
  end
180
225
 
181
226
  # Perform decryption using Vault. This will raise exceptions if Vault is
182
227
  # unavailable.
183
- def vault_decrypt(path, key, ciphertext, client)
228
+ def vault_decrypt(path, key, ciphertext, client: , context: nil)
184
229
  return nil if ciphertext.nil?
185
230
 
186
- route = File.join(path, "decrypt", key)
187
- secret = client.logical.write(route, ciphertext: ciphertext)
188
- return Base64.strict_decode64(secret.data[:plaintext])
189
- end
231
+ route = File.join(path, "decrypt", key)
190
232
 
191
- # The symmetric key for the given params.
192
- # @return [String]
193
- def memory_key_for(path, key)
194
- return Base64.strict_encode64("#{path}/#{key}".ljust(32, "x"))
233
+ data = { ciphertext: ciphertext }
234
+ data[:context] = Base64.strict_encode64(context) if context
235
+
236
+ secret = client.logical.write(route, data)
237
+
238
+ return Base64.strict_decode64(secret.data[:plaintext])
195
239
  end
196
240
 
197
241
  # Forces the encoding into the default Rails encoding and returns the
@@ -227,6 +271,10 @@ module Vault
227
271
  ::Rails.logger.warn { msg }
228
272
  end
229
273
  end
274
+
275
+ def transform_role_name(opts)
276
+ opts[:role] || self.default_role_name || self.application
277
+ end
230
278
  end
231
279
  end
232
280
  end
@@ -10,10 +10,13 @@ module Vault
10
10
  #
11
11
  # @return [String]
12
12
  def application
13
- if !defined?(@application) || @application.nil?
14
- raise RuntimeError, "Must set `Vault::Rails#application'!"
13
+ if defined?(@application) && !@application.nil?
14
+ return @application
15
15
  end
16
- return @application
16
+ if ENV.has_key?("VAULT_RAILS_APPLICATION")
17
+ return ENV["VAULT_RAILS_APPLICATION"]
18
+ end
19
+ raise RuntimeError, "Must set `Vault::Rails#application'!"
17
20
  end
18
21
 
19
22
  # Set the name of the application.
@@ -30,10 +33,13 @@ module Vault
30
33
  #
31
34
  # @return [true, false]
32
35
  def enabled?
33
- if !defined?(@enabled) || @enabled.nil?
34
- return false
36
+ if defined?(@enabled) && !@enabled.nil?
37
+ return @enabled
38
+ end
39
+ if ENV.has_key?("VAULT_RAILS_ENABLED")
40
+ return (ENV["VAULT_RAILS_ENABLED"] == "true")
35
41
  end
36
- return @enabled
42
+ return false
37
43
  end
38
44
 
39
45
  # Sets whether Vault is enabled. Users can set this in an initializer
@@ -112,13 +118,27 @@ module Vault
112
118
  @retry_max_wait ||= Vault::Defaults::RETRY_MAX_WAIT
113
119
  end
114
120
 
115
- # Sets the naximum amount of time for a single retry. Please see the Vault
121
+ # Sets the maximum amount of time for a single retry. Please see the Vault
116
122
  # documentation for more information.
117
123
  #
118
124
  # @param [Fixnum] val
119
125
  def retry_max_wait=(val)
120
126
  @retry_max_wait = val
121
127
  end
128
+
129
+ # Gets the default role name.
130
+ #
131
+ # @return [String]
132
+ def default_role_name
133
+ @default_role_name
134
+ end
135
+
136
+ # Sets the default role to use with various plugins.
137
+ #
138
+ # @param [String] val
139
+ def default_role_name=(val)
140
+ @default_role_name = val
141
+ end
122
142
  end
123
143
  end
124
144
  end
@@ -14,6 +14,14 @@ module Vault
14
14
  end
15
15
  end
16
16
 
17
+ class InvalidCiphertext < VaultRailsError
18
+ def initialize(ciphertext)
19
+ super <<~EOH
20
+ Invalid ciphertext: `#{ciphertext}'.
21
+ EOH
22
+ end
23
+ end
24
+
17
25
  class ValidationFailedError < VaultRailsError; end
18
26
  end
19
27
  end
@@ -7,17 +7,16 @@ module Vault
7
7
  }.freeze
8
8
 
9
9
  def self.encode(raw)
10
- self._init!
11
-
12
- raw = {} if raw.nil?
10
+ _init!
13
11
 
14
12
  JSON.fast_generate(raw)
15
13
  end
16
14
 
17
15
  def self.decode(raw)
18
- self._init!
16
+ _init!
17
+
18
+ return nil if raw == nil || raw == ""
19
19
 
20
- return {} if raw.nil? || raw.empty?
21
20
  JSON.parse(raw, DECODE_OPTIONS)
22
21
  end
23
22
 
@@ -1,5 +1,5 @@
1
1
  module Vault
2
2
  module Rails
3
- VERSION = "0.3.2"
3
+ VERSION = "0.7.1"
4
4
  end
5
5
  end
@@ -25,4 +25,24 @@ class LazyPerson < ActiveRecord::Base
25
25
  decode: ->(raw) { raw && raw[3...-3] }
26
26
 
27
27
  vault_attribute :non_ascii
28
+
29
+ vault_attribute :default,
30
+ default: "abc123"
31
+
32
+ vault_attribute :default_with_serializer,
33
+ serialize: :json,
34
+ default: {}
35
+
36
+ vault_attribute :context_string,
37
+ context: "production"
38
+
39
+ vault_attribute :context_symbol,
40
+ context: :encryption_context
41
+
42
+ vault_attribute :context_proc,
43
+ context: ->(record) { record.encryption_context }
44
+
45
+ def encryption_context
46
+ "user_#{id}"
47
+ end
28
48
  end
@@ -0,0 +1,18 @@
1
+
2
+ class LazySinglePerson < ActiveRecord::Base
3
+ include Vault::EncryptedModel
4
+
5
+ self.table_name = "people"
6
+
7
+ vault_lazy_decrypt!
8
+ vault_single_decrypt!
9
+
10
+ vault_attribute :ssn
11
+
12
+ vault_attribute :credit_card,
13
+ encrypted_column: :cc_encrypted
14
+
15
+ def encryption_context
16
+ "user_#{id}"
17
+ end
18
+ end
@@ -21,5 +21,40 @@ class Person < ActiveRecord::Base
21
21
  decode: ->(raw) { raw && raw[3...-3] }
22
22
 
23
23
  vault_attribute :non_ascii
24
- end
25
24
 
25
+ vault_attribute :default,
26
+ default: "abc123"
27
+
28
+ vault_attribute :default_with_serializer,
29
+ serialize: :json,
30
+ default: {}
31
+
32
+ vault_attribute :context_string,
33
+ context: "production"
34
+
35
+ vault_attribute :context_symbol,
36
+ context: :encryption_context
37
+
38
+ vault_attribute :context_proc,
39
+ context: ->(record) { record.encryption_context }
40
+
41
+ vault_attribute :transform_ssn,
42
+ transform_secret: {
43
+ transformation: "social_sec"
44
+ }
45
+
46
+ vault_attribute :bad_transform,
47
+ transform_secret: {
48
+ transformation: "foobar_transformation"
49
+ }
50
+
51
+ vault_attribute :bad_role_transform,
52
+ transform_secret: {
53
+ transformation: "social_sec",
54
+ role: "foobar_role"
55
+ }
56
+
57
+ def encryption_context
58
+ "user_#{id}"
59
+ end
60
+ end
@@ -16,9 +16,6 @@ Rails.application.configure do
16
16
  # Don't care if the mailer can't send.
17
17
  config.action_mailer.raise_delivery_errors = false
18
18
 
19
- # Print deprecation notices to the Rails logger.
20
- config.active_support.deprecation = :log
21
-
22
19
  # Raise an error on page load if there are pending migrations.
23
20
  config.active_record.migration_error = :page_load
24
21
 
@@ -36,4 +33,9 @@ Rails.application.configure do
36
33
 
37
34
  # Raises error for missing translations
38
35
  # config.action_view.raise_on_missing_translations = true
36
+
37
+ # Use native SQLite integers as booleans in Rails 5.2+
38
+ if ActiveRecord.gem_version >= Gem::Version.new("5.2")
39
+ config.active_record.sqlite3.represent_boolean_as_integer = true
40
+ end
39
41
  end
@@ -36,9 +36,11 @@ Rails.application.configure do
36
36
  # ActionMailer::Base.deliveries array.
37
37
  config.action_mailer.delivery_method = :test
38
38
 
39
- # Print deprecation notices to the stderr.
40
- config.active_support.deprecation = :stderr
41
-
42
39
  # Raises error for missing translations
43
40
  # config.action_view.raise_on_missing_translations = true
41
+
42
+ # Use native SQLite integers as booleans in Rails 5.2+
43
+ if ActiveRecord.gem_version >= Gem::Version.new("5.2")
44
+ config.active_record.sqlite3.represent_boolean_as_integer = true
45
+ end
44
46
  end
@@ -1,4 +1,4 @@
1
- class CreatePeople < ActiveRecord::Migration
1
+ class CreatePeople < ActiveRecord::Migration[4.2]
2
2
  def change
3
3
  create_table :people do |t|
4
4
  t.string :name
@@ -8,6 +8,12 @@ class CreatePeople < ActiveRecord::Migration
8
8
  t.string :business_card_encrypted
9
9
  t.string :favorite_color_encrypted
10
10
  t.string :non_ascii_encrypted
11
+ t.string :default_encrypted
12
+ t.string :default_with_serializer_encrypted
13
+ t.string :context_string_encrypted
14
+ t.string :context_symbol_encrypted
15
+ t.string :context_proc_encrypted
16
+ t.string :transform_ssn_encrypted
11
17
 
12
18
  t.timestamps null: false
13
19
  end
@@ -1,28 +1,33 @@
1
- # encoding: UTF-8
2
1
  # This file is auto-generated from the current state of the database. Instead
3
2
  # of editing this file, please use the migrations feature of Active Record to
4
3
  # incrementally modify your database, and then regenerate this schema definition.
5
4
  #
6
- # Note that this schema.rb definition is the authoritative source for your
7
- # database schema. If you need to create the application database on another
8
- # system, you should be using db:schema:load, not running all the migrations
9
- # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
- # you'll amass, the slower it'll run and the greater likelihood for issues).
5
+ # This file is the source Rails uses to define your schema when running `rails
6
+ # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
11
10
  #
12
11
  # It's strongly recommended that you check this file into your version control system.
13
12
 
14
- ActiveRecord::Schema.define(version: 20150428220101) do
13
+ ActiveRecord::Schema.define(version: 2015_04_28_220101) do
15
14
 
16
15
  create_table "people", force: :cascade do |t|
17
- t.string "name"
18
- t.string "ssn_encrypted"
19
- t.string "cc_encrypted"
20
- t.string "details_encrypted"
21
- t.string "business_card_encrypted"
22
- t.string "favorite_color_encrypted"
23
- t.string "non_ascii_encrypted"
24
- t.datetime "created_at", null: false
25
- t.datetime "updated_at", null: false
16
+ t.string "name"
17
+ t.string "ssn_encrypted"
18
+ t.string "cc_encrypted"
19
+ t.string "details_encrypted"
20
+ t.string "business_card_encrypted"
21
+ t.string "favorite_color_encrypted"
22
+ t.string "non_ascii_encrypted"
23
+ t.string "default_encrypted"
24
+ t.string "default_with_serializer_encrypted"
25
+ t.string "context_string_encrypted"
26
+ t.string "context_symbol_encrypted"
27
+ t.string "context_proc_encrypted"
28
+ t.string "transform_ssn_encrypted"
29
+ t.datetime "created_at", null: false
30
+ t.datetime "updated_at", null: false
26
31
  end
27
32
 
28
33
  end