vault-rails 0.6.0 → 0.7.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
2
  SHA256:
3
- metadata.gz: a77c9fca886e05e6f2d085f1d1c2108e3362a008d206064e5096a963ccf20e9f
4
- data.tar.gz: 79c838d9af6fb2bbb490b86aa2653267162d00dd8adc9cb2219f4f55c24fb3de
3
+ metadata.gz: 60cc2b942cbd53163fc7effc3b0f47afc268f203ce079c444635ca9b4f14cc3a
4
+ data.tar.gz: d1c4e963e55205851a0e866eae50067f0a6c4839b53327ae6bb190d6ba98dd7f
5
5
  SHA512:
6
- metadata.gz: 393da926db908ac314bef3ed19034634b0f7eef69f36cc4052e36990c03c7fc36194cafdc8eafce61acac758b12218cbdd77aca9f35f345509b918d44abdb68b
7
- data.tar.gz: a57640064fa2aafc9193edd65bca73419e1e50cf53177914f27646accc4d8b24dda401e20b1ab3e2b2eab5fe8a8e0cafd963f8a89605c09a75c6d8657cddf773
6
+ metadata.gz: d08dbab43552537a3380e798efc70ba83694ac6e41bbd3e564c22ae0aead476c9080cf57685b9ac3a4e0719769ff91ce93a72c5bdd64b1a7593700a4306530a8
7
+ data.tar.gz: 594a5b3017457cee3369553b118ab67a105cabc42d7d6b52c4130953d954ec8063b4d52392088ce67f31a07ed6800134d1a5819c18378b312ede905ce9caea69
data/README.md CHANGED
@@ -1,9 +1,11 @@
1
- Vault Rails [![Build Status](https://secure.travis-ci.org/hashicorp/vault-rails.svg?branch=master)](http://travis-ci.org/hashicorp/vault-rails)
1
+ Vault Rails [![Build Status](https://circleci.com/gh/hashicorp/vault-rails.svg?style=shield)](https://circleci.com/gh/hashicorp/vault-rails)
2
2
  ===========
3
3
 
4
4
  Vault is the official Rails plugin for interacting with [Vault](https://vaultproject.io) by HashiCorp.
5
5
 
6
- **The documentation in this README corresponds to the master branch of the Vault Rails plugin. It may contain unreleased features or different APIs than the most recently released version. Please see the Git tag that corresponds to your version of the Vault Rails plugin for the proper documentation.**
6
+ **If you're viewing this README from GitHub on the `master` branch, know that it may contain unreleased features or
7
+ different APIs than the most recently released version. Please see the Git tag that corresponds to your version of the
8
+ Vault Rails plugin for the proper documentation.**
7
9
 
8
10
  ## Table of Contents
9
11
  1. [Quick Start](#quick-start)
@@ -19,7 +21,7 @@ Quick Start
19
21
  1. Add to your Gemfile:
20
22
 
21
23
  ```ruby
22
- gem "vault-rails", "~> 0.1", require: false
24
+ gem "vault-rails", require: false
23
25
  ```
24
26
 
25
27
  and then run the `bundle` command to install.
@@ -81,6 +83,7 @@ Quick Start
81
83
  person.save #=> true
82
84
  person.ssn_encrypted #=> "vault:v0:EE3EV8P5hyo9h..."
83
85
  ```
86
+ - **Note** The unencrypted value will still be saved if the attribute referenced has a corresponding column. (i.e. `ssn` in the case above)
84
87
 
85
88
 
86
89
  Advanced Configuration
data/Rakefile CHANGED
@@ -11,6 +11,9 @@ Bundler::GemHelper.install_tasks
11
11
  APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
12
12
  load "rails/tasks/engine.rake"
13
13
 
14
- require "rspec/core/rake_task"
15
- RSpec::Core::RakeTask.new(:spec)
16
14
  task default: :spec
15
+
16
+ require "rspec/core/rake_task"
17
+ RSpec::Core::RakeTask.new(:spec) do |t|
18
+ puts "\n==> Testing with Rails #{Rails::VERSION::STRING} and Ruby #{RUBY_VERSION} <==\n"
19
+ end
@@ -41,31 +41,22 @@ module Vault
41
41
  # a proc to encode the value with
42
42
  # @option options [Proc] :decode
43
43
  # a proc to decode the value with
44
+ # @option options [Hash] :transform_secret
45
+ # a hash providing details about the transformation to use,
46
+ # this includes the name, and the role to use
44
47
  def vault_attribute(attribute, options = {})
45
- encrypted_column = options[:encrypted_column] || "#{attribute}_encrypted"
46
- path = options[:path] || "transit"
47
- key = options[:key] || "#{Vault::Rails.application}_#{table_name}_#{attribute}"
48
- context = options[:context]
49
- default = options[:default]
50
-
51
48
  # Sanity check options!
52
49
  _vault_validate_options!(options)
53
50
 
54
- # Get the serializer if one was given.
55
- serializer = options[:serialize]
51
+ parsed_opts = if options[:transform_secret]
52
+ parse_transform_secret_attributes(attribute, options)
53
+ else
54
+ parse_transit_attributes(attribute, options)
55
+ end
56
+ parsed_opts[:encrypted_column] = options[:encrypted_column] || "#{attribute}_encrypted"
56
57
 
57
- # Unless a class or module was given, construct our serializer. (Slass
58
- # is a subset of Module).
59
- if serializer && !serializer.is_a?(Module)
60
- serializer = Vault::Rails.serializer_for(serializer)
61
- end
62
-
63
- # See if custom encoding or decoding options were given.
64
- if options[:encode] && options[:decode]
65
- serializer = Class.new
66
- serializer.define_singleton_method(:encode, &options[:encode])
67
- serializer.define_singleton_method(:decode, &options[:decode])
68
- end
58
+ # Make a note of this attribute so we can use it in the future (maybe).
59
+ __vault_attributes[attribute.to_sym] = parsed_opts
69
60
 
70
61
  self.attribute attribute.to_s, ActiveRecord::Type::Value.new,
71
62
  default: nil
@@ -82,7 +73,7 @@ module Vault
82
73
 
83
74
  # We always set it as changed without comparing with the current value
84
75
  # because we allow our held values to be mutated, so we need to assume
85
- # that if you call attr=, you want it send back regardless.
76
+ # that if you call attr=, you want it sent back regardless.
86
77
 
87
78
  attribute_will_change!("#{attribute}")
88
79
  instance_variable_set("@#{attribute}", value)
@@ -98,16 +89,6 @@ module Vault
98
89
  instance_variable_get("@#{attribute}").present?
99
90
  end
100
91
 
101
- # Make a note of this attribute so we can use it in the future (maybe).
102
- __vault_attributes[attribute.to_sym] = {
103
- context: context,
104
- default: default,
105
- encrypted_column: encrypted_column,
106
- key: key,
107
- path: path,
108
- serializer: serializer
109
- }
110
-
111
92
  self
112
93
  end
113
94
 
@@ -126,6 +107,11 @@ module Vault
126
107
  raise Vault::Rails::ValidationFailedError, "Cannot use a " \
127
108
  "custom encoder/decoder if a `:serializer' is specified!"
128
109
  end
110
+
111
+ if options[:transform_secret]
112
+ raise Vault::Rails::ValidationFailedError, "Cannot use the " \
113
+ "transform secrets engine with a specified `:serializer'!"
114
+ end
129
115
  end
130
116
 
131
117
  if options[:encode] && !options[:decode]
@@ -144,6 +130,12 @@ module Vault
144
130
  "`:context' must take 1 argument!"
145
131
  end
146
132
  end
133
+ if transform_opts = options[:transform_secret]
134
+ if !transform_opts[:transformation]
135
+ raise Vault::Rails::VaildationFailedError, "Transform Secrets " \
136
+ "requires a transformation name!"
137
+ end
138
+ end
147
139
  end
148
140
 
149
141
  def vault_lazy_decrypt
@@ -161,6 +153,54 @@ module Vault
161
153
  def vault_single_decrypt!
162
154
  @vault_single_decrypt = true
163
155
  end
156
+
157
+ private
158
+
159
+ def parse_transform_secret_attributes(attribute, options)
160
+ opts = {}
161
+ opts[:transform_secret] = true
162
+
163
+ serializer = Class.new
164
+ serializer.define_singleton_method(:encode) do |raw|
165
+ return if raw.nil?
166
+ resp = Vault::Rails.transform_encode(raw, options[:transform_secret])
167
+ resp.dig(:data, :encoded_value)
168
+ end
169
+ serializer.define_singleton_method(:decode) do |raw|
170
+ return if raw.nil?
171
+ resp = Vault::Rails.transform_decode(raw, options[:transform_secret])
172
+ resp.dig(:data, :decoded_value)
173
+ end
174
+ opts[:serializer] = serializer
175
+ opts
176
+ end
177
+
178
+ def parse_transit_attributes(attribute, options)
179
+ opts = {}
180
+ opts[:path] = options[:path] || "transit"
181
+ opts[:key] = options[:key] || "#{Vault::Rails.application}_#{table_name}_#{attribute}"
182
+ opts[:context] = options[:context]
183
+ opts[:default] = options[:default]
184
+
185
+ # Get the serializer if one was given.
186
+ serializer = options[:serialize]
187
+
188
+ # Unless a class or module was given, construct our serializer. (Slass
189
+ # is a subset of Module).
190
+ if serializer && !serializer.is_a?(Module)
191
+ serializer = Vault::Rails.serializer_for(serializer)
192
+ end
193
+
194
+ # See if custom encoding or decoding options were given.
195
+ if options[:encode] && options[:decode]
196
+ serializer = Class.new
197
+ serializer.define_singleton_method(:encode, &options[:encode])
198
+ serializer.define_singleton_method(:decode, &options[:decode])
199
+ end
200
+
201
+ opts[:serializer] = serializer
202
+ opts
203
+ end
164
204
  end
165
205
 
166
206
  included do
@@ -176,6 +216,12 @@ module Vault
176
216
  # before theirs, resulting in attributes that are not persisted.
177
217
  after_save :__vault_persist_attributes!
178
218
 
219
+ # Before destroying a record, ensure that all vault attributes have been
220
+ # decrypted. Otherwise, attempting to read a lazily decrypted attribute
221
+ # after a destroy will result in a "Can't modify frozen Hash" error when
222
+ # vault-rails attempts to set the plain-text attribute.
223
+ before_destroy :__vault_load_attributes!, unless: -> { @__vault_loaded }
224
+
179
225
  # Decrypt all the attributes from Vault.
180
226
  # @return [true]
181
227
  def __vault_initialize_attributes!
@@ -196,7 +242,7 @@ module Vault
196
242
  self.__vault_load_attribute!(attribute, options)
197
243
  end
198
244
 
199
- @__vault_loaded = self.class.__vault_attributes.all? { |attribute, __| instance_variable_get("@#{attribute}") }
245
+ @__vault_loaded = self.class.__vault_attributes.all? { |attribute, __| instance_variable_defined?("@#{attribute}") }
200
246
 
201
247
  return true
202
248
  end
@@ -209,6 +255,7 @@ module Vault
209
255
  column = options[:encrypted_column]
210
256
  context = options[:context]
211
257
  default = options[:default]
258
+ transform = options[:transform_secret]
212
259
 
213
260
  # Load the ciphertext
214
261
  ciphertext = read_attribute(column)
@@ -222,11 +269,18 @@ module Vault
222
269
  # Generate context if needed
223
270
  generated_context = __vault_generate_context(context)
224
271
 
225
- # Load the plaintext value
226
- plaintext = Vault::Rails.decrypt(
227
- path, key, ciphertext,
228
- context: generated_context
229
- )
272
+ if transform
273
+ # If this is a secret encrypted with FPE, we do not need to decrypt with vault
274
+ # This prevents a double encryption via standard vault encryption and FPE.
275
+ # FPE is decrypted later as part of the serializer
276
+ plaintext = ciphertext
277
+ else
278
+ # Load the plaintext value
279
+ plaintext = Vault::Rails.decrypt(
280
+ path, key, ciphertext,
281
+ context: generated_context
282
+ )
283
+ end
230
284
 
231
285
  # Deserialize the plaintext value, if a serializer exists
232
286
  if serializer
@@ -273,6 +327,7 @@ module Vault
273
327
  serializer = options[:serializer]
274
328
  column = options[:encrypted_column]
275
329
  context = options[:context]
330
+ transform = options[:transform_secret]
276
331
 
277
332
  # Only persist changed attributes to minimize requests - this helps
278
333
  # minimize the number of requests to Vault.
@@ -297,11 +352,18 @@ module Vault
297
352
  # Generate context if needed
298
353
  generated_context = __vault_generate_context(context)
299
354
 
300
- # Generate the ciphertext and store it back as an attribute
301
- ciphertext = Vault::Rails.encrypt(
302
- path, key, plaintext,
303
- context: generated_context
304
- )
355
+ if transform
356
+ # If this is a secret encrypted with FPE, we should not encrypt it in vault
357
+ # This prevents a double encryption via standard vault encryption and FPE.
358
+ # FPE was performed earlier as part of the serialization process.
359
+ ciphertext = plaintext
360
+ else
361
+ # Generate the ciphertext and store it back as an attribute
362
+ ciphertext = Vault::Rails.encrypt(
363
+ path, key, plaintext,
364
+ context: generated_context
365
+ )
366
+ end
305
367
 
306
368
  # Write the attribute back, so that we don't have to reload the record
307
369
  # to get the ciphertext
@@ -141,6 +141,34 @@ module Vault
141
141
  end
142
142
  end
143
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
+
144
172
  protected
145
173
 
146
174
  # Perform in-memory encryption. This is useful for testing and development.
@@ -243,6 +271,10 @@ module Vault
243
271
  ::Rails.logger.warn { msg }
244
272
  end
245
273
  end
274
+
275
+ def transform_role_name(opts)
276
+ opts[:role] || self.default_role_name || self.application
277
+ end
246
278
  end
247
279
  end
248
280
  end
@@ -125,6 +125,20 @@ module Vault
125
125
  def retry_max_wait=(val)
126
126
  @retry_max_wait = val
127
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
128
142
  end
129
143
  end
130
144
  end
@@ -1,5 +1,5 @@
1
1
  module Vault
2
2
  module Rails
3
- VERSION = "0.6.0"
3
+ VERSION = "0.7.0"
4
4
  end
5
5
  end
@@ -38,6 +38,22 @@ class Person < ActiveRecord::Base
38
38
  vault_attribute :context_proc,
39
39
  context: ->(record) { record.encryption_context }
40
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
+
41
57
  def encryption_context
42
58
  "user_#{id}"
43
59
  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
@@ -13,6 +13,7 @@ class CreatePeople < ActiveRecord::Migration[4.2]
13
13
  t.string :context_string_encrypted
14
14
  t.string :context_symbol_encrypted
15
15
  t.string :context_proc_encrypted
16
+ t.string :transform_ssn_encrypted
16
17
 
17
18
  t.timestamps null: false
18
19
  end
@@ -2,11 +2,11 @@
2
2
  # of editing this file, please use the migrations feature of Active Record to
3
3
  # incrementally modify your database, and then regenerate this schema definition.
4
4
  #
5
- # Note that this schema.rb definition is the authoritative source for your
6
- # database schema. If you need to create the application database on another
7
- # system, you should be using db:schema:load, not running all the migrations
8
- # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
- # 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.
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
@@ -25,6 +25,7 @@ ActiveRecord::Schema.define(version: 2015_04_28_220101) do
25
25
  t.string "context_string_encrypted"
26
26
  t.string "context_symbol_encrypted"
27
27
  t.string "context_proc_encrypted"
28
+ t.string "transform_ssn_encrypted"
28
29
  t.datetime "created_at", null: false
29
30
  t.datetime "updated_at", null: false
30
31
  end