Skip to content

Commit df0941f

Browse files
p-mongop
andauthored
MONGOID-5319 Permit driver FLE configuration to be specified in Mongoid config file (#5251)
* MONGOID-5319 Permit driver FLE configuration to be specified in Mongoid config file * remove documentation for loading schema map from file Co-authored-by: Oleg Pudeyev <[email protected]>
1 parent 24fc402 commit df0941f

17 files changed

+330
-17
lines changed

docs/reference/configuration.txt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,44 @@ be executed sequentially during socket creation.
732732
For more information about TLS context hooks, including best practices for
733733
assigning and removing them, see `the Ruby driver documentation <https://mongodb.com/docs/ruby-driver/current/reference/create-client/#modifying-sslcontext>`_.
734734

735+
736+
Client-Side Encryption
737+
======================
738+
739+
When loading the configuration file, Mongoid permits the file to contain
740+
``BSON::Binary`` instances which are used for specifying ``keyId`` in
741+
the schema map for `client-side encryption
742+
<https://www.mongodb.com/docs/ruby-driver/current/reference/client-side-encryption/>`_,
743+
as the following example shows:
744+
745+
.. code-block:: yaml
746+
747+
development:
748+
clients:
749+
default:
750+
database: blog_development
751+
hosts: [localhost:27017]
752+
options:
753+
auto_encryption_options:
754+
key_vault_namespace: 'keyvault.datakeys'
755+
kms_providers:
756+
local:
757+
key: "z7iYiYKLuYymEWtk4kfny1ESBwwFdA58qMqff96A8ghiOcIK75lJGPUIocku8LOFjQuEgeIP4xlln3s7r93FV9J5sAE7zg8U"
758+
schema_map:
759+
blog_development.comments:
760+
properties:
761+
message:
762+
encrypt:
763+
keyId:
764+
- !ruby/object:BSON::Binary
765+
data: !binary |-
766+
R/AgNcxASFiiJWKXqWGo5w==
767+
type: :uuid
768+
bsonType: "string"
769+
algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
770+
bsonType: "object"
771+
772+
735773
Usage with Forking Servers
736774
==========================
737775

lib/mongoid/config/environment.rb

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,37 @@ def env_name
4444
# override the current Mongoid environment.
4545
#
4646
# @return [ Hash ] The settings.
47+
#
4748
# @api private
4849
def load_yaml(path, environment = nil)
4950
env = environment ? environment.to_s : env_name
50-
contents = File.new(path).read
51+
52+
contents = File.read(path)
5153
if contents.empty?
5254
raise Mongoid::Errors::EmptyConfigFile.new(path)
5355
end
54-
data = if RUBY_VERSION.start_with?("2.5")
55-
YAML.safe_load(ERB.new(contents).result, [Symbol], [], true)
56+
57+
# These are the classes that can be used in a Mongoid
58+
# configuration file in addition to standard YAML types.
59+
permitted_classes = [
60+
# Symbols occur as values for read preference, for example.
61+
Symbol,
62+
# BSON::Binary occur as keyId values for FLE (more precisely,
63+
# the keyIds are UUIDs).
64+
BSON::Binary,
65+
]
66+
67+
result = ERB.new(contents).result
68+
data = if RUBY_VERSION < '2.6'
69+
YAML.safe_load(result, permitted_classes, [], true)
5670
else
57-
YAML.safe_load(ERB.new(contents).result, permitted_classes: [Symbol], aliases: true)
71+
YAML.safe_load(result, permitted_classes: permitted_classes, aliases: true)
5872
end
73+
5974
unless data.is_a?(Hash)
6075
raise Mongoid::Errors::InvalidConfigFile.new(path)
6176
end
77+
6278
data[env]
6379
end
6480
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
test:
2+
clients:
3+
default:
4+
database: mongoid_test
5+
hosts:
6+
<% SpecConfig.instance.addresses.each do |address| %>
7+
- <%= address %>
8+
<% end %>
9+
options:
10+
auto_encryption_options:
11+
key_vault_namespace: 'admin.datakeys'
12+
kms_providers:
13+
local:
14+
key: "z7iYiYKLuYymEWtk4kfny1ESBwwFdA58qMqff96A8ghiOcIK75lJGPUIocku8LOFjQuEgeIP4xlln3s7r93FV9J5sAE7zg8U"
15+
schema_map:
16+
blog_development.comments:
17+
properties:
18+
message:
19+
encrypt:
20+
keyId:
21+
- !ruby/object:BSON::Binary
22+
data: !binary |-
23+
R/AgNcxASFiiJWKXqWGo5w==
24+
type: :uuid
25+
bsonType: "string"
26+
algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
27+
bsonType: "object"

spec/mongoid/clients/factory_spec.rb

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
context "when the configuration exists" do
3737

3838
context "when the configuration is standard" do
39+
restore_config_clients
3940

4041
let(:config) do
4142
{
@@ -46,7 +47,6 @@
4647

4748
before do
4849
Mongoid::Config.send(:clients=, config)
49-
# TODO: We should restore overwritten configuration in after block
5050
end
5151

5252
after do
@@ -118,6 +118,7 @@
118118
end
119119

120120
context "when the configuration has no ports" do
121+
restore_config_clients
121122

122123
let(:config) do
123124
{
@@ -128,7 +129,6 @@
128129

129130
before do
130131
Mongoid::Config.send(:clients=, config)
131-
# TODO: We should restore overwritten configuration in after block
132132
end
133133

134134
after do
@@ -163,6 +163,7 @@
163163
context "when configured via a uri" do
164164

165165
context "when the uri has a single host:port" do
166+
restore_config_clients
166167

167168
let(:config) do
168169
{
@@ -173,7 +174,6 @@
173174

174175
before do
175176
Mongoid::Config.send(:clients=, config)
176-
# TODO: We should restore overwritten configuration in after block
177177
end
178178

179179
after do
@@ -202,6 +202,7 @@
202202
end
203203

204204
context "when the uri has multiple host:port pairs" do
205+
restore_config_clients
205206

206207
let(:config) do
207208
{
@@ -212,7 +213,6 @@
212213

213214
before do
214215
Mongoid::Config.send(:clients=, config)
215-
# TODO: We should restore overwritten configuration in after block
216216
end
217217

218218
after do
@@ -253,14 +253,14 @@
253253
end
254254

255255
context "when no name is provided" do
256+
restore_config_clients
256257

257258
let(:config) do
258259
{ default: { hosts: SpecConfig.instance.addresses, database: database_id }}
259260
end
260261

261262
before do
262263
Mongoid::Config.send(:clients=, config)
263-
# TODO: We should restore overwritten configuration in after block
264264
end
265265

266266
after do
@@ -287,12 +287,12 @@
287287
end
288288

289289
context "when nil is provided and no default config" do
290+
restore_config_clients
290291

291292
let(:config) { nil }
292293

293294
before do
294295
Mongoid.clients[:default] = nil
295-
# TODO: We should restore overwritten configuration in after block
296296
end
297297

298298
it "raises NoClientsConfig error" do
@@ -302,14 +302,14 @@
302302
end
303303

304304
describe ".default" do
305+
restore_config_clients
305306

306307
let(:config) do
307308
{ default: { hosts: SpecConfig.instance.addresses, database: database_id }}
308309
end
309310

310311
before do
311312
Mongoid::Config.send(:clients=, config)
312-
# TODO: We should restore overwritten configuration in after block
313313
end
314314

315315
after do
@@ -336,6 +336,7 @@
336336
end
337337

338338
context "when options are provided with string keys" do
339+
restore_config_clients
339340

340341
let(:config) do
341342
{
@@ -352,7 +353,6 @@
352353

353354
before do
354355
Mongoid::Config.send(:clients=, config)
355-
# TODO: We should restore overwritten configuration in after block
356356
end
357357

358358
after do
@@ -391,6 +391,8 @@
391391
end
392392

393393
context "unexpected config options" do
394+
restore_config_clients
395+
394396
let(:unknown_opts) do
395397
{
396398
bad_one: 1,
@@ -408,11 +410,8 @@
408410
}
409411
end
410412

411-
around(:each) do |example|
412-
old_config = Mongoid::Config.clients
413+
before do
413414
Mongoid::Config.send(:clients=, config)
414-
example.run
415-
Mongoid::Config.send(:clients=, old_config)
416415
end
417416

418417
[:bad_one, :bad_two].each do |env|

spec/mongoid/config/environment_spec.rb

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def environment; :staging; end
106106

107107
context 'when file found' do
108108
before do
109-
allow(File).to receive(:new).with('mongoid.yml').and_return(StringIO.new(file_contents))
109+
allow(File).to receive(:read).with('mongoid.yml').and_return(file_contents)
110110
end
111111

112112
let(:file_contents) do
@@ -162,5 +162,43 @@ def environment; :staging; end
162162
it { is_expected.to be_nil }
163163
end
164164
end
165+
166+
context 'when configuration includes schema map' do
167+
paths = Dir.glob(File.join(File.dirname(__FILE__), '../../support/schema_maps/*.json'))
168+
169+
if paths.empty?
170+
raise "Expected to find some schema maps"
171+
end
172+
173+
before do
174+
allow(File).to receive(:read).with('mongoid.yml').and_return(file_contents)
175+
end
176+
177+
let(:file_contents) do
178+
<<~FILE
179+
test:
180+
clients:
181+
default:
182+
database: mongoid_test
183+
hosts: [localhost]
184+
options:
185+
auto_encryption_options:
186+
schema_map: #{schema_map.to_yaml.sub(/\A---/, '').gsub(/\n/, "\n" + ' '*100)}
187+
FILE
188+
end
189+
190+
paths.each do |path|
191+
context File.basename(path) do
192+
let(:schema_map) do
193+
BSON::ExtJSON.parse(File.read(path))
194+
end
195+
196+
it 'loads successfully' do
197+
subject.should be_a(Hash)
198+
subject.fetch('clients').fetch('default').fetch('options').fetch('auto_encryption_options').fetch('schema_map').should be_a(Hash)
199+
end
200+
end
201+
end
202+
end
165203
end
166204
end

spec/mongoid/config_spec.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,46 @@ def self.logger
544544
end
545545
end
546546
end
547+
548+
context 'when schema map is provided with uuid' do
549+
let(:file) do
550+
File.join(File.dirname(__FILE__), "..", "config", "mongoid_with_schema_map_uuid.yml")
551+
end
552+
553+
before do
554+
described_class.load!(file, :test)
555+
end
556+
557+
let(:client) { Mongoid.default_client }
558+
559+
# Wrapping libraries are only recognized by driver 2.13.0+.
560+
min_driver_version '2.13'
561+
562+
it 'passes uuid to driver' do
563+
Mongo::Client.should receive(:new).with(SpecConfig.instance.addresses,
564+
auto_encryption_options: {
565+
'key_vault_namespace' => 'admin.datakeys',
566+
'kms_providers' => {'local' => {'key' => 'z7iYiYKLuYymEWtk4kfny1ESBwwFdA58qMqff96A8ghiOcIK75lJGPUIocku8LOFjQuEgeIP4xlln3s7r93FV9J5sAE7zg8U'}},
567+
'schema_map' => {'blog_development.comments' => {
568+
'bsonType' => 'object',
569+
'properties' => {
570+
'message' => {'encrypt' => {
571+
'algorithm' => 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic',
572+
'bsonType' => 'string',
573+
'keyId' => [BSON::Binary.new("G\xF0 5\xCC@HX\xA2%b\x97\xA9a\xA8\xE7", :uuid)],
574+
}},
575+
},
576+
}}},
577+
database: 'mongoid_test',
578+
platform: "mongoid-#{Mongoid::VERSION}",
579+
wrapping_libraries: [
580+
{'name' => 'Mongoid', 'version' => Mongoid::VERSION},
581+
],
582+
)
583+
584+
client
585+
end
586+
end
547587
end
548588

549589
describe "#options=" do

spec/support/macros.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,14 @@ def with_config_values(key, *values, &block)
3838
end
3939
end
4040
end
41+
42+
def restore_config_clients
43+
around do |example|
44+
# Duplicate the config because some tests mutate it.
45+
old_config = Mongoid::Config.clients.dup
46+
example.run
47+
Mongoid::Config.send(:clients=, old_config)
48+
end
49+
end
4150
end
4251
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"properties": {
3+
"ssn": {
4+
"encrypt": {
5+
"keyId": [{
6+
"$binary": {
7+
"base64": "AWSAAAAAAAAAAAAAAAAAAA==",
8+
"subType": "04"
9+
}
10+
}],
11+
"bsonType": "string",
12+
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
13+
}
14+
}
15+
},
16+
"bsonType": "object"
17+
}

0 commit comments

Comments
 (0)