mongo 2.24.1 → 2.25.0
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.
- checksums.yaml +4 -4
- data/bin/mongo_console +2 -2
- data/lib/mongo/address/validator.rb +1 -1
- data/lib/mongo/address.rb +1 -3
- data/lib/mongo/auth/aws/conversation.rb +0 -4
- data/lib/mongo/auth/aws/credentials_retriever.rb +3 -8
- data/lib/mongo/auth/base.rb +1 -1
- data/lib/mongo/auth/sasl_conversation_base.rb +1 -1
- data/lib/mongo/auth/scram_conversation_base.rb +1 -8
- data/lib/mongo/auth/stringprep.rb +1 -6
- data/lib/mongo/auth/user/view.rb +1 -1
- data/lib/mongo/bulk_write/result_combiner.rb +3 -3
- data/lib/mongo/bulk_write.rb +1 -1
- data/lib/mongo/client.rb +82 -12
- data/lib/mongo/cluster/reapers/cursor_reaper.rb +12 -5
- data/lib/mongo/cluster/sdam_flow.rb +4 -4
- data/lib/mongo/cluster/topology/base.rb +1 -1
- data/lib/mongo/cluster.rb +12 -28
- data/lib/mongo/collection/view/aggregation/behavior.rb +1 -1
- data/lib/mongo/collection/view/aggregation.rb +2 -4
- data/lib/mongo/collection/view/builder/aggregation.rb +3 -5
- data/lib/mongo/collection/view/builder/map_reduce.rb +1 -1
- data/lib/mongo/collection/view/change_stream.rb +14 -9
- data/lib/mongo/collection/view/map_reduce.rb +2 -11
- data/lib/mongo/collection/view/readable.rb +11 -7
- data/lib/mongo/collection/view/writable.rb +4 -4
- data/lib/mongo/collection/view.rb +8 -4
- data/lib/mongo/collection.rb +1 -1
- data/lib/mongo/crypt/auto_encrypter.rb +1 -1
- data/lib/mongo/crypt/binary.rb +7 -4
- data/lib/mongo/crypt/binding.rb +88 -10
- data/lib/mongo/crypt/context.rb +50 -19
- data/lib/mongo/crypt/encryption_io.rb +11 -5
- data/lib/mongo/crypt/handle.rb +17 -5
- data/lib/mongo/crypt/kms/credentials.rb +70 -31
- data/lib/mongo/crypt/kms/kmip/master_document.rb +30 -1
- data/lib/mongo/crypt/kms/master_key_document.rb +11 -6
- data/lib/mongo/crypt/kms.rb +12 -1
- data/lib/mongo/cursor.rb +8 -2
- data/lib/mongo/database/cursor_command_view.rb +95 -0
- data/lib/mongo/database.rb +144 -18
- data/lib/mongo/error/invalid_uri.rb +5 -1
- data/lib/mongo/error/operation_failure.rb +2 -2
- data/lib/mongo/error/parser.rb +3 -3
- data/lib/mongo/error/sdam_error_detection.rb +2 -3
- data/lib/mongo/grid/fs_bucket.rb +27 -21
- data/lib/mongo/grid/stream/write.rb +2 -2
- data/lib/mongo/index/view.rb +13 -7
- data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_closed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_created.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_ready.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_cleared.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_closed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_created.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_ready.rb +1 -0
- data/lib/mongo/monitoring/event/command_failed.rb +1 -0
- data/lib/mongo/monitoring/event/command_started.rb +1 -0
- data/lib/mongo/monitoring/event/command_succeeded.rb +1 -0
- data/lib/mongo/monitoring/event/secure.rb +12 -17
- data/lib/mongo/monitoring/event/server_closed.rb +1 -0
- data/lib/mongo/monitoring/event/server_description_changed.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_started.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +1 -0
- data/lib/mongo/monitoring/event/server_opening.rb +1 -0
- data/lib/mongo/monitoring/event/topology_changed.rb +1 -0
- data/lib/mongo/monitoring/event/topology_closed.rb +1 -0
- data/lib/mongo/monitoring/event/topology_opening.rb +1 -0
- data/lib/mongo/operation/create/op_msg.rb +1 -0
- data/lib/mongo/operation/create_index/op_msg.rb +2 -1
- data/lib/mongo/operation/cursor_command/op_msg.rb +37 -0
- data/lib/mongo/operation/cursor_command/result.rb +60 -0
- data/lib/mongo/operation/cursor_command.rb +33 -0
- data/lib/mongo/operation/delete/op_msg.rb +1 -0
- data/lib/mongo/operation/drop/op_msg.rb +1 -0
- data/lib/mongo/operation/drop_database/op_msg.rb +1 -0
- data/lib/mongo/operation/drop_index/op_msg.rb +1 -0
- data/lib/mongo/operation/find/builder/command.rb +1 -1
- data/lib/mongo/operation/insert/bulk_result.rb +2 -4
- data/lib/mongo/operation/insert/op_msg.rb +1 -0
- data/lib/mongo/operation/result.rb +12 -25
- data/lib/mongo/operation/shared/causal_consistency_supported.rb +4 -2
- data/lib/mongo/operation/shared/executable.rb +1 -9
- data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
- data/lib/mongo/operation/shared/sessions_supported.rb +3 -5
- data/lib/mongo/operation/update/bulk_result.rb +1 -1
- data/lib/mongo/operation/update/op_msg.rb +1 -0
- data/lib/mongo/operation/write_command/op_msg.rb +2 -0
- data/lib/mongo/operation.rb +1 -0
- data/lib/mongo/options/redacted.rb +2 -2
- data/lib/mongo/protocol/compressed.rb +15 -11
- data/lib/mongo/protocol/message.rb +16 -16
- data/lib/mongo/protocol/registry.rb +1 -1
- data/lib/mongo/protocol/serializers.rb +1 -3
- data/lib/mongo/query_cache.rb +3 -5
- data/lib/mongo/server/connection.rb +3 -1
- data/lib/mongo/server/connection_common.rb +9 -1
- data/lib/mongo/server/connection_pool/generation_manager.rb +1 -1
- data/lib/mongo/server/connection_pool.rb +24 -10
- data/lib/mongo/server/description/features.rb +1 -1
- data/lib/mongo/server/description.rb +1 -1
- data/lib/mongo/server/monitor/connection.rb +3 -2
- data/lib/mongo/server/monitor.rb +133 -30
- data/lib/mongo/server/pending_connection.rb +1 -0
- data/lib/mongo/server/push_monitor.rb +16 -2
- data/lib/mongo/server.rb +27 -7
- data/lib/mongo/server_selector/base.rb +3 -6
- data/lib/mongo/server_selector/secondary_preferred.rb +4 -1
- data/lib/mongo/session.rb +22 -23
- data/lib/mongo/socket/ssl.rb +3 -3
- data/lib/mongo/socket.rb +1 -7
- data/lib/mongo/srv/monitor.rb +11 -2
- data/lib/mongo/srv/resolver.rb +1 -1
- data/lib/mongo/srv/result.rb +5 -3
- data/lib/mongo/timeout.rb +10 -15
- data/lib/mongo/tracing/open_telemetry/command_tracer.rb +16 -2
- data/lib/mongo/uri/options_mapper.rb +21 -9
- data/lib/mongo/uri/srv_protocol.rb +4 -4
- data/lib/mongo/uri.rb +56 -8
- data/lib/mongo/utils.rb +1 -1
- data/lib/mongo/version.rb +1 -1
- metadata +6 -4
- data/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +0 -173
- data/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +0 -1164
|
@@ -678,7 +678,7 @@ module Mongo
|
|
|
678
678
|
# @api private
|
|
679
679
|
def read_concern
|
|
680
680
|
if options[:session] && options[:session].in_transaction?
|
|
681
|
-
options[:session].
|
|
681
|
+
options[:session].txn_read_concern || collection.client.read_concern
|
|
682
682
|
else
|
|
683
683
|
collection.read_concern
|
|
684
684
|
end
|
|
@@ -749,12 +749,10 @@ module Mongo
|
|
|
749
749
|
end
|
|
750
750
|
end
|
|
751
751
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
end
|
|
757
|
-
|
|
752
|
+
# The server selector for this view, derived from its read
|
|
753
|
+
# preference (or the collection/client default).
|
|
754
|
+
#
|
|
755
|
+
# @api private
|
|
758
756
|
def server_selector
|
|
759
757
|
@server_selector ||= if options[:session] && options[:session].in_transaction?
|
|
760
758
|
ServerSelector.get(read_preference || client.server_selector)
|
|
@@ -763,6 +761,12 @@ module Mongo
|
|
|
763
761
|
end
|
|
764
762
|
end
|
|
765
763
|
|
|
764
|
+
private
|
|
765
|
+
|
|
766
|
+
def collation(doc = nil)
|
|
767
|
+
configure(:collation, doc)
|
|
768
|
+
end
|
|
769
|
+
|
|
766
770
|
def validate_doc!(doc)
|
|
767
771
|
raise Error::InvalidDocument.new unless doc.respond_to?(:keys)
|
|
768
772
|
end
|
|
@@ -624,8 +624,8 @@ module Mongo
|
|
|
624
624
|
# @raise [ Error::InvalidUpdateDocument ] if the first key in the
|
|
625
625
|
# document does not start with a $.
|
|
626
626
|
def validate_update_documents!(spec)
|
|
627
|
-
return unless update = spec.is_a?(Array) ? spec&.first : spec
|
|
628
|
-
return unless key = update.keys&.first
|
|
627
|
+
return unless (update = spec.is_a?(Array) ? spec&.first : spec)
|
|
628
|
+
return unless (key = update.keys&.first)
|
|
629
629
|
return if key.to_s.start_with?('$')
|
|
630
630
|
raise Error::InvalidUpdateDocument.new(key: key) if Mongo.validate_update_replace
|
|
631
631
|
|
|
@@ -641,8 +641,8 @@ module Mongo
|
|
|
641
641
|
# @raise [ Error::InvalidUpdateDocument ] if the first key in the
|
|
642
642
|
# document does not start with a $.
|
|
643
643
|
def validate_replacement_documents!(spec)
|
|
644
|
-
return unless replace = spec.is_a?(Array) ? spec&.first : spec
|
|
645
|
-
return unless key = replace.keys&.first
|
|
644
|
+
return unless (replace = spec.is_a?(Array) ? spec&.first : spec)
|
|
645
|
+
return unless (key = replace.keys&.first)
|
|
646
646
|
return unless key.to_s.start_with?('$')
|
|
647
647
|
raise Error::InvalidReplacementDocument.new(key: key) if Mongo.validate_update_replace
|
|
648
648
|
|
|
@@ -236,6 +236,14 @@ module Mongo
|
|
|
236
236
|
end
|
|
237
237
|
end
|
|
238
238
|
|
|
239
|
+
# Executes the provided block within the context of a session, using
|
|
240
|
+
# this view's options merged with the given ones.
|
|
241
|
+
#
|
|
242
|
+
# @api private
|
|
243
|
+
def with_session(opts = {}, &block)
|
|
244
|
+
client.with_session(@options.merge(opts), &block)
|
|
245
|
+
end
|
|
246
|
+
|
|
239
247
|
private
|
|
240
248
|
|
|
241
249
|
def initialize_copy(other)
|
|
@@ -252,10 +260,6 @@ module Mongo
|
|
|
252
260
|
def view
|
|
253
261
|
self
|
|
254
262
|
end
|
|
255
|
-
|
|
256
|
-
def with_session(opts = {}, &block)
|
|
257
|
-
client.with_session(@options.merge(opts), &block)
|
|
258
|
-
end
|
|
259
263
|
end
|
|
260
264
|
end
|
|
261
265
|
end
|
data/lib/mongo/collection.rb
CHANGED
|
@@ -393,7 +393,7 @@ module Mongo
|
|
|
393
393
|
operation = { create: name }.merge(options)
|
|
394
394
|
operation.delete(:write)
|
|
395
395
|
operation.delete(:write_concern)
|
|
396
|
-
client.
|
|
396
|
+
client.with_session(opts) do |session|
|
|
397
397
|
write_concern = if opts[:write_concern]
|
|
398
398
|
WriteConcern.get(opts[:write_concern])
|
|
399
399
|
else
|
data/lib/mongo/crypt/binary.rb
CHANGED
|
@@ -139,14 +139,17 @@ module Mongo
|
|
|
139
139
|
# Wraps a String with a mongocrypt_binary_t, yielding an FFI::Pointer
|
|
140
140
|
# to the wrapped struct.
|
|
141
141
|
def self.wrap_string(str)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
# mongocrypt_binary_new_from_data does not copy the buffer, so the
|
|
143
|
+
# MemoryPointer must outlive the mongocrypt_binary_t. Hold it in a
|
|
144
|
+
# local so it stays referenced for the whole method frame; otherwise
|
|
145
|
+
# GC can free the buffer while libmongocrypt still points into it.
|
|
146
|
+
data_p = FFI::MemoryPointer.from_string(str)
|
|
147
|
+
binary_p = Binding.mongocrypt_binary_new_from_data(data_p, str.bytesize)
|
|
146
148
|
begin
|
|
147
149
|
yield binary_p
|
|
148
150
|
ensure
|
|
149
151
|
Binding.mongocrypt_binary_destroy(binary_p)
|
|
152
|
+
data_p # rubocop:disable Lint/Void -- keep the buffer alive past destroy
|
|
150
153
|
end
|
|
151
154
|
end
|
|
152
155
|
end
|
data/lib/mongo/crypt/binding.rb
CHANGED
|
@@ -106,7 +106,7 @@ module Mongo
|
|
|
106
106
|
def self.parse_version(version)
|
|
107
107
|
Gem::Version.new(version)
|
|
108
108
|
rescue ArgumentError
|
|
109
|
-
match = version.match(/\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?(
|
|
109
|
+
match = version.match(/\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)?(?:-[A-Za-z+\d]+)?\z/)
|
|
110
110
|
raise ArgumentError.new("Malformed version number string #{version}") if match.nil?
|
|
111
111
|
|
|
112
112
|
Gem::Version.new(
|
|
@@ -823,14 +823,15 @@ module Mongo
|
|
|
823
823
|
|
|
824
824
|
# An enum labeling different libmognocrypt state machine states
|
|
825
825
|
enum :mongocrypt_ctx_state, [
|
|
826
|
-
:error,
|
|
827
|
-
:need_mongo_collinfo,
|
|
828
|
-
:need_mongo_markings,
|
|
829
|
-
:need_mongo_keys,
|
|
830
|
-
:need_kms,
|
|
831
|
-
:ready,
|
|
832
|
-
:done,
|
|
833
|
-
:need_kms_credentials,
|
|
826
|
+
:error, 0,
|
|
827
|
+
:need_mongo_collinfo, 1,
|
|
828
|
+
:need_mongo_markings, 2,
|
|
829
|
+
:need_mongo_keys, 3,
|
|
830
|
+
:need_kms, 4,
|
|
831
|
+
:ready, 5,
|
|
832
|
+
:done, 6,
|
|
833
|
+
:need_kms_credentials, 7,
|
|
834
|
+
:need_mongo_collinfo_with_db, 8,
|
|
834
835
|
]
|
|
835
836
|
|
|
836
837
|
# @!method self.mongocrypt_ctx_state(ctx)
|
|
@@ -1105,10 +1106,20 @@ module Mongo
|
|
|
1105
1106
|
def self.check_kms_ctx_status(kms_context)
|
|
1106
1107
|
return if yield
|
|
1107
1108
|
|
|
1109
|
+
kms_ctx_status(kms_context).raise_crypt_error(kms: true)
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
# Read the status information for the given KmsContext into a Status
|
|
1113
|
+
# object without raising.
|
|
1114
|
+
#
|
|
1115
|
+
# @param [ Mongo::Crypt::KmsContext ] kms_context
|
|
1116
|
+
#
|
|
1117
|
+
# @return [ Mongo::Crypt::Status ] The KMS context status.
|
|
1118
|
+
def self.kms_ctx_status(kms_context)
|
|
1108
1119
|
status = Status.new
|
|
1109
1120
|
|
|
1110
1121
|
mongocrypt_kms_ctx_status(kms_context.kms_ctx_p, status.ref)
|
|
1111
|
-
status
|
|
1122
|
+
status
|
|
1112
1123
|
end
|
|
1113
1124
|
|
|
1114
1125
|
# @!method self.mongocrypt_kms_ctx_usleep(ctx)
|
|
@@ -1646,6 +1657,73 @@ module Mongo
|
|
|
1646
1657
|
mongocrypt_setopt_use_need_kms_credentials_state(handle.ref)
|
|
1647
1658
|
end
|
|
1648
1659
|
|
|
1660
|
+
# @!method self.mongocrypt_setopt_enable_multiple_collinfo(crypt)
|
|
1661
|
+
# @api private
|
|
1662
|
+
#
|
|
1663
|
+
# Enable support for multiple collection schemas. Required to support $lookup.
|
|
1664
|
+
#
|
|
1665
|
+
# @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
|
|
1666
|
+
# @pre mongocrypt_init has not been called on crypt.
|
|
1667
|
+
# @return [ Boolean ] Whether the option was set successfully.
|
|
1668
|
+
attach_function(
|
|
1669
|
+
:mongocrypt_setopt_enable_multiple_collinfo,
|
|
1670
|
+
[ :pointer ],
|
|
1671
|
+
:bool
|
|
1672
|
+
)
|
|
1673
|
+
|
|
1674
|
+
# Enable support for multiple collection schemas. Required to support $lookup.
|
|
1675
|
+
#
|
|
1676
|
+
# @param [ Mongo::Crypt::Handle ] handle
|
|
1677
|
+
# @return [ Boolean ] Whether the option was set successfully.
|
|
1678
|
+
def self.setopt_enable_multiple_collinfo(handle)
|
|
1679
|
+
check_status(handle) do
|
|
1680
|
+
mongocrypt_setopt_enable_multiple_collinfo(handle.ref)
|
|
1681
|
+
end
|
|
1682
|
+
end
|
|
1683
|
+
|
|
1684
|
+
# @!method self.mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(crypt)
|
|
1685
|
+
# @api private
|
|
1686
|
+
#
|
|
1687
|
+
# Opt into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state.
|
|
1688
|
+
# A context enters this state when processing a bulkWrite command whose
|
|
1689
|
+
# target database differs from the command database ("admin").
|
|
1690
|
+
#
|
|
1691
|
+
# @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object.
|
|
1692
|
+
attach_function(
|
|
1693
|
+
:mongocrypt_setopt_use_need_mongo_collinfo_with_db_state,
|
|
1694
|
+
[ :pointer ],
|
|
1695
|
+
:void
|
|
1696
|
+
)
|
|
1697
|
+
|
|
1698
|
+
# Opt into handling the MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state.
|
|
1699
|
+
#
|
|
1700
|
+
# @param [ Mongo::Crypt::Handle ] handle
|
|
1701
|
+
def self.setopt_use_need_mongo_collinfo_with_db_state(handle)
|
|
1702
|
+
mongocrypt_setopt_use_need_mongo_collinfo_with_db_state(handle.ref)
|
|
1703
|
+
end
|
|
1704
|
+
|
|
1705
|
+
# @!method self.mongocrypt_ctx_mongo_db(ctx)
|
|
1706
|
+
# @api private
|
|
1707
|
+
#
|
|
1708
|
+
# Get the database name for the current collinfo operation. Used in the
|
|
1709
|
+
# MONGOCRYPT_CTX_NEED_MONGO_COLLINFO_WITH_DB state to determine which
|
|
1710
|
+
# database to run listCollections against.
|
|
1711
|
+
#
|
|
1712
|
+
# @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object.
|
|
1713
|
+
# @return [ String, nil ] The database name, or nil on error.
|
|
1714
|
+
attach_function :mongocrypt_ctx_mongo_db, [ :pointer ], :string
|
|
1715
|
+
|
|
1716
|
+
# Get the database name for the current collinfo operation.
|
|
1717
|
+
#
|
|
1718
|
+
# @param [ Mongo::Crypt::Context ] context
|
|
1719
|
+
# @return [ String ] The database name.
|
|
1720
|
+
def self.ctx_mongo_db(context)
|
|
1721
|
+
db_name = mongocrypt_ctx_mongo_db(context.ctx_p)
|
|
1722
|
+
return db_name if db_name
|
|
1723
|
+
|
|
1724
|
+
check_ctx_status(context)
|
|
1725
|
+
end
|
|
1726
|
+
|
|
1649
1727
|
# @!method self.mongocrypt_ctx_provide_kms_providers(ctx, kms_providers)
|
|
1650
1728
|
# @api private
|
|
1651
1729
|
#
|
data/lib/mongo/crypt/context.rb
CHANGED
|
@@ -76,7 +76,7 @@ module Mongo
|
|
|
76
76
|
# This method is not currently unit tested. It is integration tested
|
|
77
77
|
# in spec/integration/explicit_encryption_spec.rb
|
|
78
78
|
def run_state_machine(timeout_holder)
|
|
79
|
-
|
|
79
|
+
loop do
|
|
80
80
|
timeout_ms = timeout_holder.remaining_timeout_ms!
|
|
81
81
|
case state
|
|
82
82
|
when :error
|
|
@@ -90,6 +90,8 @@ module Mongo
|
|
|
90
90
|
provide_keys(timeout_ms)
|
|
91
91
|
when :need_mongo_collinfo
|
|
92
92
|
provide_collection_info(timeout_ms)
|
|
93
|
+
when :need_mongo_collinfo_with_db
|
|
94
|
+
provide_collection_info_with_db(timeout_ms)
|
|
93
95
|
when :need_mongo_markings
|
|
94
96
|
provide_markings(timeout_ms)
|
|
95
97
|
when :need_kms
|
|
@@ -122,17 +124,26 @@ module Mongo
|
|
|
122
124
|
def provide_markings(timeout_ms)
|
|
123
125
|
cmd = Binding.ctx_mongo_op(self)
|
|
124
126
|
|
|
125
|
-
result = @encryption_io.mark_command(cmd, timeout_ms: timeout_ms)
|
|
127
|
+
result = @encryption_io.mark_command(cmd, db_name: @db_name, timeout_ms: timeout_ms)
|
|
126
128
|
mongocrypt_feed(result)
|
|
127
129
|
|
|
128
130
|
mongocrypt_done
|
|
129
131
|
end
|
|
130
132
|
|
|
131
133
|
def provide_collection_info(timeout_ms)
|
|
134
|
+
feed_collection_info(@db_name, timeout_ms)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def provide_collection_info_with_db(timeout_ms)
|
|
138
|
+
feed_collection_info(Binding.ctx_mongo_db(self), timeout_ms)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def feed_collection_info(db_name, timeout_ms)
|
|
132
142
|
filter = Binding.ctx_mongo_op(self)
|
|
133
143
|
|
|
134
|
-
|
|
135
|
-
|
|
144
|
+
@encryption_io.collection_info(db_name, filter, timeout_ms: timeout_ms).each do |result|
|
|
145
|
+
mongocrypt_feed(result)
|
|
146
|
+
end
|
|
136
147
|
|
|
137
148
|
mongocrypt_done
|
|
138
149
|
end
|
|
@@ -159,12 +170,29 @@ module Mongo
|
|
|
159
170
|
raise unless e.network_error?
|
|
160
171
|
next if Binding.kms_ctx_fail(kms_context)
|
|
161
172
|
|
|
162
|
-
|
|
173
|
+
raise_kms_retry_error(kms_context, e)
|
|
163
174
|
end
|
|
164
175
|
end
|
|
165
176
|
Binding.ctx_kms_done(self)
|
|
166
177
|
end
|
|
167
178
|
|
|
179
|
+
# Raise a KmsError that wraps the KMS status message (which describes the
|
|
180
|
+
# retry exhaustion) with the error from the last attempt.
|
|
181
|
+
#
|
|
182
|
+
# @param [ Mongo::Crypt::KmsContext ] kms_context
|
|
183
|
+
# @param [ Mongo::Error::KmsError ] last_error The error from the last
|
|
184
|
+
# KMS request attempt.
|
|
185
|
+
#
|
|
186
|
+
# @raise [ Mongo::Error::KmsError ]
|
|
187
|
+
def raise_kms_retry_error(kms_context, last_error)
|
|
188
|
+
status = Binding.kms_ctx_status(kms_context)
|
|
189
|
+
raise Error::KmsError.new(
|
|
190
|
+
"#{status.message}, last attempt failed with: #{last_error.message}",
|
|
191
|
+
code: status.code,
|
|
192
|
+
network_error: true
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
|
|
168
196
|
# Indicate that state machine is done feeding I/O responses back to libmongocrypt
|
|
169
197
|
def mongocrypt_done
|
|
170
198
|
Binding.mongocrypt_ctx_mongo_done(ctx_p)
|
|
@@ -188,21 +216,24 @@ module Mongo
|
|
|
188
216
|
# KMS providers.
|
|
189
217
|
def retrieve_kms_credentials(timeout_holder)
|
|
190
218
|
providers = {}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
219
|
+
kms_providers.credentials_map.each do |identifier, creds|
|
|
220
|
+
next unless creds.empty?
|
|
221
|
+
|
|
222
|
+
case KMS.provider_base_type(identifier)
|
|
223
|
+
when 'aws'
|
|
224
|
+
begin
|
|
225
|
+
aws_credentials = Mongo::Auth::Aws::CredentialsRetriever.new.credentials(timeout_holder)
|
|
226
|
+
rescue Auth::Aws::CredentialsNotFound
|
|
227
|
+
raise Error::CryptError.new(
|
|
228
|
+
'Could not locate AWS credentials (checked environment variables, ECS and EC2 metadata)'
|
|
229
|
+
)
|
|
230
|
+
end
|
|
231
|
+
providers[identifier] = aws_credentials.to_h
|
|
232
|
+
when 'gcp'
|
|
233
|
+
providers[identifier] = { access_token: gcp_access_token(timeout_holder) }
|
|
234
|
+
when 'azure'
|
|
235
|
+
providers[identifier] = { access_token: azure_access_token(timeout_holder) }
|
|
198
236
|
end
|
|
199
|
-
providers[:aws] = aws_credentials.to_h
|
|
200
|
-
end
|
|
201
|
-
if kms_providers.gcp && kms_providers.gcp.empty?
|
|
202
|
-
providers[:gcp] = { access_token: gcp_access_token(timeout_holder) }
|
|
203
|
-
end
|
|
204
|
-
if kms_providers.azure && kms_providers.azure.empty?
|
|
205
|
-
providers[:azure] = { access_token: azure_access_token(timeout_holder) }
|
|
206
237
|
end
|
|
207
238
|
KMS::Credentials.new(providers)
|
|
208
239
|
end
|
|
@@ -97,7 +97,7 @@ module Mongo
|
|
|
97
97
|
# Must be a non-negative integer. An explicit value of 0 means infinite.
|
|
98
98
|
# The default value is unset which means the feature is not enabled.
|
|
99
99
|
#
|
|
100
|
-
# @return [
|
|
100
|
+
# @return [ Array<BSON::Document> ] The collection information documents
|
|
101
101
|
def collection_info(db_name, filter, timeout_ms: nil)
|
|
102
102
|
unless @metadata_client
|
|
103
103
|
raise ArgumentError,
|
|
@@ -108,18 +108,22 @@ module Mongo
|
|
|
108
108
|
.use(db_name)
|
|
109
109
|
.database
|
|
110
110
|
.list_collections(filter: filter, deserialize_as_bson: true, timeout_ms: timeout_ms)
|
|
111
|
-
.
|
|
111
|
+
.to_a
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
# Send the command to mongocryptd to be marked with intent-to-encrypt markings
|
|
115
115
|
#
|
|
116
116
|
# @param [ Hash ] cmd
|
|
117
|
+
# @param [ String | nil ] :db_name The database against which the command
|
|
118
|
+
# is being run. When provided, the command is sent to mongocryptd using
|
|
119
|
+
# this database so that the namespace in the command matches the namespace
|
|
120
|
+
# in encryptionInformation.
|
|
117
121
|
# @param [ Integer ] :timeout_ms The operation timeout in milliseconds.
|
|
118
122
|
# Must be a non-negative integer. An explicit value of 0 means infinite.
|
|
119
123
|
# The default value is unset which means the feature is not enabled.
|
|
120
124
|
#
|
|
121
125
|
# @return [ Hash ] The marked command
|
|
122
|
-
def mark_command(cmd, timeout_ms: nil)
|
|
126
|
+
def mark_command(cmd, db_name: nil, timeout_ms: nil)
|
|
123
127
|
unless @mongocryptd_client
|
|
124
128
|
raise ArgumentError,
|
|
125
129
|
'mark_command requires mongocryptd_client to have been passed to the constructor, but it was not'
|
|
@@ -132,13 +136,15 @@ module Mongo
|
|
|
132
136
|
timeout_ms: timeout_ms
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
mongocryptd = db_name ? @mongocryptd_client.use(db_name) : @mongocryptd_client
|
|
140
|
+
|
|
135
141
|
begin
|
|
136
|
-
response =
|
|
142
|
+
response = mongocryptd.database.command(cmd, options)
|
|
137
143
|
rescue Error::NoServerAvailable => e
|
|
138
144
|
raise e if @options[:mongocryptd_bypass_spawn]
|
|
139
145
|
|
|
140
146
|
spawn_mongocryptd
|
|
141
|
-
response =
|
|
147
|
+
response = mongocryptd.database.command(cmd, options)
|
|
142
148
|
end
|
|
143
149
|
|
|
144
150
|
response.first
|
data/lib/mongo/crypt/handle.rb
CHANGED
|
@@ -74,6 +74,8 @@ module Mongo
|
|
|
74
74
|
Binding.method(:mongocrypt_destroy)
|
|
75
75
|
)
|
|
76
76
|
Binding.kms_ctx_setopt_retry_kms(self, true)
|
|
77
|
+
Binding.setopt_enable_multiple_collinfo(self)
|
|
78
|
+
Binding.setopt_use_need_mongo_collinfo_with_db_state(self)
|
|
77
79
|
@kms_providers = kms_providers
|
|
78
80
|
@kms_tls_options = kms_tls_options
|
|
79
81
|
|
|
@@ -101,7 +103,7 @@ module Mongo
|
|
|
101
103
|
|
|
102
104
|
Binding.setopt_kms_providers(self, @kms_providers.to_document)
|
|
103
105
|
|
|
104
|
-
if @kms_providers.
|
|
106
|
+
if @kms_providers.any_on_demand?
|
|
105
107
|
Binding.setopt_use_need_kms_credentials_state(self)
|
|
106
108
|
end
|
|
107
109
|
|
|
@@ -123,13 +125,23 @@ module Mongo
|
|
|
123
125
|
end
|
|
124
126
|
|
|
125
127
|
# Return TLS options for KMS provider. If there are no TLS options set,
|
|
126
|
-
# empty hash is returned.
|
|
128
|
+
# empty hash is returned. Named providers (e.g. "kmip:name1") fall back
|
|
129
|
+
# to the base-type options (e.g. :kmip) when no exact match is found.
|
|
127
130
|
#
|
|
128
|
-
# @param [ String ] provider
|
|
131
|
+
# @param [ String ] provider KMS provider name or named identifier.
|
|
129
132
|
#
|
|
130
133
|
# @return [ Hash ] TLS options to connect to KMS provider.
|
|
131
134
|
def kms_tls_options(provider)
|
|
132
|
-
|
|
135
|
+
provider_str = provider.to_s
|
|
136
|
+
base_type = KMS.provider_base_type(provider_str)
|
|
137
|
+
|
|
138
|
+
@kms_tls_options.fetch(provider_str) do
|
|
139
|
+
@kms_tls_options.fetch(provider_str.to_sym) do
|
|
140
|
+
@kms_tls_options.fetch(base_type) do
|
|
141
|
+
@kms_tls_options.fetch(base_type.to_sym, {})
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
133
145
|
end
|
|
134
146
|
|
|
135
147
|
def crypt_shared_lib_version
|
|
@@ -191,7 +203,7 @@ module Mongo
|
|
|
191
203
|
# Send the logs from libmongocrypt to the Mongo::Logger
|
|
192
204
|
def set_logger_callback
|
|
193
205
|
@log_callback = proc do |level, msg|
|
|
194
|
-
@logger.
|
|
206
|
+
@logger.public_send(level, msg)
|
|
195
207
|
end
|
|
196
208
|
|
|
197
209
|
Binding.setopt_log_handler(@mongocrypt, @log_callback)
|
|
@@ -21,28 +21,17 @@ module Mongo
|
|
|
21
21
|
#
|
|
22
22
|
# @api private
|
|
23
23
|
class Credentials
|
|
24
|
-
#
|
|
25
|
-
|
|
24
|
+
# KMS provider types that support on-demand credential retrieval.
|
|
25
|
+
ON_DEMAND_PROVIDERS = %w[aws gcp azure].freeze
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
attr_reader :azure
|
|
27
|
+
attr_reader :credentials_map
|
|
29
28
|
|
|
30
|
-
#
|
|
31
|
-
attr_reader :gcp
|
|
32
|
-
|
|
33
|
-
# @return [ Credentials::KMIP | nil ] KMIP KMS credentials.
|
|
34
|
-
attr_reader :kmip
|
|
35
|
-
|
|
36
|
-
# @return [ Credentials::Local | nil ] Local KMS credentials.
|
|
37
|
-
attr_reader :local
|
|
38
|
-
|
|
39
|
-
# Creates a KMS credentials object form a parameters hash.
|
|
29
|
+
# Creates a KMS credentials object from a parameters hash.
|
|
40
30
|
#
|
|
41
|
-
# @param [ Hash ] kms_providers A hash that contains
|
|
42
|
-
# KMS providers.
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# classes inside Mongo::Crypt::KMS module.
|
|
31
|
+
# @param [ Hash ] kms_providers A hash that contains credentials for
|
|
32
|
+
# KMS providers. Keys may be provider types (:aws, :local, etc.) or
|
|
33
|
+
# named provider identifiers ("aws:name1", "local:name2", etc.).
|
|
34
|
+
# Values are hashes of credentials for the corresponding provider type.
|
|
46
35
|
#
|
|
47
36
|
# @note There may be more than one KMS provider specified.
|
|
48
37
|
#
|
|
@@ -51,29 +40,79 @@ module Mongo
|
|
|
51
40
|
def initialize(kms_providers)
|
|
52
41
|
raise ArgumentError.new('KMS providers options must not be nil') if kms_providers.nil?
|
|
53
42
|
|
|
54
|
-
@
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
@credentials_map = {}
|
|
44
|
+
|
|
45
|
+
kms_providers.each do |identifier, opts|
|
|
46
|
+
identifier_str = identifier.to_s
|
|
47
|
+
provider_type = KMS.provider_base_type(identifier_str)
|
|
48
|
+
|
|
49
|
+
creds = case provider_type
|
|
50
|
+
when 'aws' then AWS::Credentials.new(opts)
|
|
51
|
+
when 'azure' then Azure::Credentials.new(opts)
|
|
52
|
+
when 'gcp' then GCP::Credentials.new(opts)
|
|
53
|
+
when 'kmip' then KMIP::Credentials.new(opts)
|
|
54
|
+
when 'local' then Local::Credentials.new(opts)
|
|
55
|
+
else
|
|
56
|
+
raise ArgumentError.new(
|
|
57
|
+
'KMS providers options must have one of the following keys: ' \
|
|
58
|
+
':aws, :azure, :gcp, :kmip, :local'
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
@credentials_map[identifier_str] = creds
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
return unless @credentials_map.empty?
|
|
60
66
|
|
|
61
67
|
raise ArgumentError.new(
|
|
62
|
-
'KMS providers options must have one of the following keys: '
|
|
68
|
+
'KMS providers options must have one of the following keys: ' \
|
|
63
69
|
':aws, :azure, :gcp, :kmip, :local'
|
|
64
70
|
)
|
|
65
71
|
end
|
|
66
72
|
|
|
73
|
+
# @return [ Credentials::AWS | nil ] AWS KMS credentials (unnamed provider only).
|
|
74
|
+
def aws
|
|
75
|
+
@credentials_map['aws']
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [ Credentials::Azure | nil ] Azure KMS credentials (unnamed provider only).
|
|
79
|
+
def azure
|
|
80
|
+
@credentials_map['azure']
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [ Credentials::GCP | nil ] GCP KMS credentials (unnamed provider only).
|
|
84
|
+
def gcp
|
|
85
|
+
@credentials_map['gcp']
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @return [ Credentials::KMIP | nil ] KMIP KMS credentials (unnamed provider only).
|
|
89
|
+
def kmip
|
|
90
|
+
@credentials_map['kmip']
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @return [ Credentials::Local | nil ] Local KMS credentials (unnamed provider only).
|
|
94
|
+
def local
|
|
95
|
+
@credentials_map['local']
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns true if any configured provider supports on-demand credential
|
|
99
|
+
# retrieval and has been configured with empty credentials.
|
|
100
|
+
#
|
|
101
|
+
# @return [ Boolean ]
|
|
102
|
+
def any_on_demand?
|
|
103
|
+
@credentials_map.any? do |identifier, creds|
|
|
104
|
+
ON_DEMAND_PROVIDERS.include?(KMS.provider_base_type(identifier)) && creds.empty?
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
67
108
|
# Convert credentials object to a BSON document in libmongocrypt format.
|
|
68
109
|
#
|
|
69
110
|
# @return [ BSON::Document ] Credentials as BSON document.
|
|
70
111
|
def to_document
|
|
71
112
|
BSON::Document.new.tap do |bson|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
bson[:kmip] = @kmip.to_document if @kmip
|
|
76
|
-
bson[:local] = @local.to_document if @local
|
|
113
|
+
@credentials_map.each do |identifier, creds|
|
|
114
|
+
bson[identifier] = creds.to_document
|
|
115
|
+
end
|
|
77
116
|
end
|
|
78
117
|
end
|
|
79
118
|
end
|
|
@@ -31,8 +31,12 @@ module Mongo
|
|
|
31
31
|
# @return [ String | nil ] KMIP KMS endpoint with optional port.
|
|
32
32
|
attr_reader :endpoint
|
|
33
33
|
|
|
34
|
+
# @return [ true | false | nil ] Whether the KMIP server performs
|
|
35
|
+
# encryption and decryption of the data key.
|
|
36
|
+
attr_reader :delegated
|
|
37
|
+
|
|
34
38
|
FORMAT_HINT = 'KMIP KMS key document must be in the format: ' +
|
|
35
|
-
"{ key_id: 'KEY-ID', endpoint: 'ENDPOINT' }"
|
|
39
|
+
"{ key_id: 'KEY-ID', endpoint: 'ENDPOINT', delegated: true|false }"
|
|
36
40
|
|
|
37
41
|
# Creates a master key document object form a parameters hash.
|
|
38
42
|
#
|
|
@@ -42,6 +46,9 @@ module Mongo
|
|
|
42
46
|
# a 96 byte KMIP Secret Data managed object, optional. If key_id
|
|
43
47
|
# is omitted, the driver creates a random 96 byte identifier.
|
|
44
48
|
# @option opts [ String | nil ] :endpoint KMIP endpoint, optional.
|
|
49
|
+
# @option opts [ true | false | nil ] :delegated If true, the KMIP
|
|
50
|
+
# server performs encryption and decryption of the data key,
|
|
51
|
+
# optional. Defaults to false.
|
|
45
52
|
#
|
|
46
53
|
# @raise [ ArgumentError ] If required options are missing or incorrectly
|
|
47
54
|
# formatted.
|
|
@@ -52,6 +59,7 @@ module Mongo
|
|
|
52
59
|
@endpoint = validate_param(
|
|
53
60
|
:endpoint, opts, FORMAT_HINT, required: false
|
|
54
61
|
)
|
|
62
|
+
@delegated = validate_delegated(opts)
|
|
55
63
|
end
|
|
56
64
|
|
|
57
65
|
# Convert master key document object to a BSON document in libmongocrypt format.
|
|
@@ -63,8 +71,29 @@ module Mongo
|
|
|
63
71
|
}).tap do |bson|
|
|
64
72
|
bson.update({ endpoint: endpoint }) unless endpoint.nil?
|
|
65
73
|
bson.update({ keyId: key_id }) unless key_id.nil?
|
|
74
|
+
bson.update({ delegated: delegated }) unless delegated.nil?
|
|
66
75
|
end
|
|
67
76
|
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# Validate the optional :delegated KMIP master key option.
|
|
81
|
+
#
|
|
82
|
+
# @param [ Hash ] opts Master key options.
|
|
83
|
+
#
|
|
84
|
+
# @return [ true | false | nil ] The delegated value, or nil if absent.
|
|
85
|
+
#
|
|
86
|
+
# @raise [ ArgumentError ] If delegated is present but not a boolean.
|
|
87
|
+
def validate_delegated(opts)
|
|
88
|
+
return nil unless opts.key?(:delegated)
|
|
89
|
+
|
|
90
|
+
value = opts[:delegated]
|
|
91
|
+
return value if value == true || value == false || value.nil?
|
|
92
|
+
|
|
93
|
+
raise ArgumentError.new(
|
|
94
|
+
"The delegated option must be a boolean; currently have #{value}"
|
|
95
|
+
)
|
|
96
|
+
end
|
|
68
97
|
end
|
|
69
98
|
end
|
|
70
99
|
end
|