mongo 2.13.0.beta1 → 2.14.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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -5
- data/Rakefile +50 -9
- data/lib/mongo.rb +13 -2
- data/lib/mongo/address.rb +1 -1
- data/lib/mongo/address/ipv4.rb +1 -1
- data/lib/mongo/address/ipv6.rb +1 -1
- data/lib/mongo/auth/aws/request.rb +31 -5
- data/lib/mongo/bulk_write.rb +18 -0
- data/lib/mongo/caching_cursor.rb +74 -0
- data/lib/mongo/client.rb +238 -31
- data/lib/mongo/cluster.rb +56 -20
- data/lib/mongo/cluster/sdam_flow.rb +13 -10
- data/lib/mongo/cluster/topology/replica_set_no_primary.rb +3 -2
- data/lib/mongo/cluster/topology/sharded.rb +1 -1
- data/lib/mongo/cluster/topology/single.rb +2 -2
- data/lib/mongo/collection.rb +66 -24
- data/lib/mongo/collection/view.rb +24 -20
- data/lib/mongo/collection/view/aggregation.rb +25 -4
- data/lib/mongo/collection/view/builder/find_command.rb +38 -18
- data/lib/mongo/collection/view/explainable.rb +27 -8
- data/lib/mongo/collection/view/iterable.rb +72 -12
- data/lib/mongo/collection/view/readable.rb +19 -3
- data/lib/mongo/collection/view/writable.rb +55 -5
- data/lib/mongo/crypt/encryption_io.rb +6 -6
- data/lib/mongo/cursor.rb +16 -3
- data/lib/mongo/database.rb +37 -4
- data/lib/mongo/database/view.rb +18 -3
- data/lib/mongo/distinguishing_semaphore.rb +55 -0
- data/lib/mongo/error.rb +5 -0
- data/lib/mongo/error/invalid_read_concern.rb +28 -0
- data/lib/mongo/error/invalid_server_auth_host.rb +22 -0
- data/lib/mongo/error/invalid_session.rb +2 -1
- data/lib/mongo/error/operation_failure.rb +11 -5
- data/lib/mongo/error/server_certificate_revoked.rb +22 -0
- data/lib/mongo/error/sessions_not_supported.rb +35 -0
- data/lib/mongo/error/unsupported_option.rb +14 -12
- data/lib/mongo/event/base.rb +6 -0
- data/lib/mongo/grid/file.rb +5 -0
- data/lib/mongo/grid/file/chunk.rb +2 -0
- data/lib/mongo/grid/fs_bucket.rb +15 -13
- data/lib/mongo/grid/stream/write.rb +9 -3
- data/lib/mongo/index/view.rb +3 -0
- data/lib/mongo/lint.rb +2 -1
- data/lib/mongo/logger.rb +3 -3
- data/lib/mongo/monitoring.rb +38 -0
- data/lib/mongo/monitoring/command_log_subscriber.rb +10 -2
- data/lib/mongo/monitoring/event/command_failed.rb +11 -0
- data/lib/mongo/monitoring/event/command_started.rb +37 -2
- data/lib/mongo/monitoring/event/command_succeeded.rb +11 -0
- data/lib/mongo/monitoring/event/server_closed.rb +1 -1
- data/lib/mongo/monitoring/event/server_description_changed.rb +27 -4
- data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +9 -2
- data/lib/mongo/monitoring/event/server_heartbeat_started.rb +9 -2
- data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +9 -2
- data/lib/mongo/monitoring/event/server_opening.rb +1 -1
- data/lib/mongo/monitoring/event/topology_changed.rb +1 -1
- data/lib/mongo/monitoring/event/topology_closed.rb +1 -1
- data/lib/mongo/monitoring/event/topology_opening.rb +1 -1
- data/lib/mongo/monitoring/publishable.rb +6 -3
- data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +9 -1
- data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +1 -1
- data/lib/mongo/operation.rb +2 -0
- data/lib/mongo/operation/aggregate/result.rb +9 -8
- data/lib/mongo/operation/collections_info/command.rb +5 -0
- data/lib/mongo/operation/collections_info/result.rb +18 -1
- data/lib/mongo/operation/delete/bulk_result.rb +2 -0
- data/lib/mongo/operation/delete/result.rb +3 -0
- data/lib/mongo/operation/explain/command.rb +4 -0
- data/lib/mongo/operation/explain/legacy.rb +4 -0
- data/lib/mongo/operation/explain/op_msg.rb +6 -0
- data/lib/mongo/operation/explain/result.rb +3 -0
- data/lib/mongo/operation/find/legacy/result.rb +2 -0
- data/lib/mongo/operation/find/result.rb +13 -0
- data/lib/mongo/operation/get_more/result.rb +3 -0
- data/lib/mongo/operation/indexes/result.rb +5 -0
- data/lib/mongo/operation/insert/bulk_result.rb +5 -0
- data/lib/mongo/operation/insert/result.rb +5 -0
- data/lib/mongo/operation/list_collections/result.rb +5 -0
- data/lib/mongo/operation/map_reduce/result.rb +10 -0
- data/lib/mongo/operation/parallel_scan/result.rb +4 -0
- data/lib/mongo/operation/result.rb +35 -6
- data/lib/mongo/operation/shared/bypass_document_validation.rb +1 -0
- data/lib/mongo/operation/shared/causal_consistency_supported.rb +1 -0
- data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +2 -0
- data/lib/mongo/operation/shared/executable.rb +1 -0
- data/lib/mongo/operation/shared/idable.rb +2 -1
- data/lib/mongo/operation/shared/limited.rb +1 -0
- data/lib/mongo/operation/shared/object_id_generator.rb +1 -0
- data/lib/mongo/operation/shared/result/aggregatable.rb +1 -0
- data/lib/mongo/operation/shared/sessions_supported.rb +1 -0
- data/lib/mongo/operation/shared/specifiable.rb +1 -0
- data/lib/mongo/operation/shared/write.rb +1 -0
- data/lib/mongo/operation/shared/write_concern_supported.rb +1 -0
- data/lib/mongo/operation/update/legacy/result.rb +7 -0
- data/lib/mongo/operation/update/result.rb +8 -0
- data/lib/mongo/operation/users_info/result.rb +3 -0
- data/lib/mongo/protocol/message.rb +47 -10
- data/lib/mongo/protocol/msg.rb +34 -1
- data/lib/mongo/protocol/query.rb +36 -0
- data/lib/mongo/protocol/serializers.rb +5 -2
- data/lib/mongo/query_cache.rb +242 -0
- data/lib/mongo/retryable.rb +8 -1
- data/lib/mongo/server.rb +15 -4
- data/lib/mongo/server/app_metadata.rb +27 -3
- data/lib/mongo/server/connection.rb +4 -4
- data/lib/mongo/server/connection_base.rb +38 -12
- data/lib/mongo/server/connection_common.rb +2 -2
- data/lib/mongo/server/connection_pool.rb +3 -0
- data/lib/mongo/server/description.rb +13 -1
- data/lib/mongo/server/monitor.rb +76 -44
- data/lib/mongo/server/monitor/connection.rb +57 -9
- data/lib/mongo/server/pending_connection.rb +14 -4
- data/lib/mongo/server/push_monitor.rb +173 -0
- data/{spec/runners/transactions/context.rb → lib/mongo/server/push_monitor/connection.rb} +9 -14
- data/lib/mongo/server_selector.rb +0 -1
- data/lib/mongo/server_selector/base.rb +583 -1
- data/lib/mongo/server_selector/nearest.rb +1 -6
- data/lib/mongo/server_selector/primary.rb +1 -6
- data/lib/mongo/server_selector/primary_preferred.rb +7 -10
- data/lib/mongo/server_selector/secondary.rb +1 -6
- data/lib/mongo/server_selector/secondary_preferred.rb +1 -7
- data/lib/mongo/session.rb +7 -1
- data/lib/mongo/socket.rb +26 -12
- data/lib/mongo/socket/ocsp_cache.rb +97 -0
- data/lib/mongo/socket/ocsp_verifier.rb +368 -0
- data/lib/mongo/socket/ssl.rb +46 -25
- data/lib/mongo/socket/tcp.rb +1 -1
- data/lib/mongo/srv/monitor.rb +7 -13
- data/lib/mongo/srv/resolver.rb +14 -10
- data/lib/mongo/timeout.rb +2 -0
- data/lib/mongo/topology_version.rb +9 -0
- data/lib/mongo/uri.rb +21 -390
- data/lib/mongo/uri/options_mapper.rb +582 -0
- data/lib/mongo/uri/srv_protocol.rb +3 -2
- data/lib/mongo/utils.rb +73 -0
- data/lib/mongo/version.rb +1 -1
- data/spec/NOTES.aws-auth.md +12 -7
- data/spec/README.aws-auth.md +2 -2
- data/spec/README.md +63 -1
- data/spec/integration/awaited_ismaster_spec.rb +28 -0
- data/spec/integration/bson_symbol_spec.rb +4 -2
- data/spec/integration/bulk_write_spec.rb +67 -0
- data/spec/integration/change_stream_examples_spec.rb +6 -2
- data/spec/integration/change_stream_spec.rb +1 -1
- data/spec/integration/check_clean_slate_spec.rb +16 -0
- data/spec/integration/client_authentication_options_spec.rb +92 -28
- data/spec/integration/client_construction_spec.rb +1 -0
- data/spec/integration/client_side_encryption/auto_encryption_bulk_writes_spec.rb +9 -5
- data/spec/integration/connect_single_rs_name_spec.rb +5 -2
- data/spec/integration/connection_pool_populator_spec.rb +4 -2
- data/spec/integration/connection_spec.rb +7 -4
- data/spec/integration/crud_spec.rb +4 -4
- data/spec/integration/cursor_reaping_spec.rb +54 -18
- data/spec/integration/docs_examples_spec.rb +6 -0
- data/spec/integration/fork_reconnect_spec.rb +56 -1
- data/spec/integration/grid_fs_bucket_spec.rb +48 -0
- data/spec/integration/heartbeat_events_spec.rb +4 -23
- data/spec/integration/ocsp_connectivity_spec.rb +26 -0
- data/spec/integration/ocsp_verifier_cache_spec.rb +188 -0
- data/spec/integration/ocsp_verifier_spec.rb +334 -0
- data/spec/integration/query_cache_spec.rb +1045 -0
- data/spec/integration/query_cache_transactions_spec.rb +190 -0
- data/spec/integration/read_concern_spec.rb +1 -1
- data/spec/integration/retryable_errors_spec.rb +1 -1
- data/spec/integration/retryable_writes/retryable_writes_40_and_newer_spec.rb +1 -0
- data/spec/integration/retryable_writes/shared/performs_legacy_retries.rb +4 -2
- data/spec/integration/retryable_writes/shared/performs_modern_retries.rb +3 -3
- data/spec/integration/retryable_writes/shared/performs_no_retries.rb +2 -2
- data/spec/integration/sdam_error_handling_spec.rb +122 -15
- data/spec/integration/sdam_events_spec.rb +80 -6
- data/spec/integration/sdam_prose_spec.rb +64 -0
- data/spec/integration/server_monitor_spec.rb +25 -1
- data/spec/integration/server_selection_spec.rb +36 -0
- data/spec/integration/size_limit_spec.rb +23 -5
- data/spec/integration/srv_monitoring_spec.rb +38 -3
- data/spec/integration/srv_spec.rb +56 -0
- data/spec/integration/ssl_uri_options_spec.rb +2 -2
- data/spec/integration/transactions_examples_spec.rb +17 -7
- data/spec/integration/zlib_compression_spec.rb +25 -0
- data/spec/lite_spec_helper.rb +20 -9
- data/spec/mongo/address_spec.rb +1 -1
- data/spec/mongo/auth/aws/request_region_spec.rb +42 -0
- data/spec/mongo/auth/aws/request_spec.rb +76 -0
- data/spec/mongo/auth/scram_spec.rb +1 -1
- data/spec/mongo/auth/user_spec.rb +1 -1
- data/spec/mongo/bulk_write_spec.rb +2 -2
- data/spec/mongo/caching_cursor_spec.rb +70 -0
- data/spec/mongo/client_construction_spec.rb +386 -3
- data/spec/mongo/client_encryption_spec.rb +16 -10
- data/spec/mongo/client_spec.rb +85 -3
- data/spec/mongo/cluster/topology/replica_set_spec.rb +53 -10
- data/spec/mongo/cluster/topology/sharded_spec.rb +1 -1
- data/spec/mongo/cluster/topology/single_spec.rb +19 -8
- data/spec/mongo/cluster/topology/unknown_spec.rb +1 -1
- data/spec/mongo/cluster/topology_spec.rb +1 -1
- data/spec/mongo/cluster_spec.rb +37 -35
- data/spec/mongo/collection/view/change_stream_resume_spec.rb +7 -7
- data/spec/mongo/collection/view/explainable_spec.rb +87 -4
- data/spec/mongo/collection/view/map_reduce_spec.rb +2 -0
- data/spec/mongo/collection/view/readable_spec.rb +36 -0
- data/spec/mongo/collection_spec.rb +572 -0
- data/spec/mongo/crypt/auto_decryption_context_spec.rb +1 -1
- data/spec/mongo/crypt/auto_encryption_context_spec.rb +1 -1
- data/spec/mongo/crypt/binary_spec.rb +1 -6
- data/spec/mongo/crypt/binding/binary_spec.rb +1 -6
- data/spec/mongo/crypt/binding/context_spec.rb +2 -7
- data/spec/mongo/crypt/binding/helpers_spec.rb +1 -6
- data/spec/mongo/crypt/binding/mongocrypt_spec.rb +2 -7
- data/spec/mongo/crypt/binding/status_spec.rb +1 -6
- data/spec/mongo/crypt/binding/version_spec.rb +1 -6
- data/spec/mongo/crypt/data_key_context_spec.rb +1 -1
- data/spec/mongo/crypt/explicit_decryption_context_spec.rb +1 -1
- data/spec/mongo/crypt/explicit_encryption_context_spec.rb +1 -1
- data/spec/mongo/crypt/status_spec.rb +1 -6
- data/spec/mongo/database_spec.rb +353 -8
- data/spec/mongo/distinguishing_semaphore_spec.rb +63 -0
- data/spec/mongo/error/no_server_available_spec.rb +1 -1
- data/spec/mongo/error/operation_failure_spec.rb +40 -0
- data/spec/mongo/index/view_spec.rb +148 -2
- data/spec/mongo/logger_spec.rb +13 -11
- data/spec/mongo/monitoring/event/server_closed_spec.rb +1 -1
- data/spec/mongo/monitoring/event/server_description_changed_spec.rb +1 -4
- data/spec/mongo/monitoring/event/server_opening_spec.rb +1 -1
- data/spec/mongo/monitoring/event/topology_changed_spec.rb +1 -1
- data/spec/mongo/monitoring/event/topology_closed_spec.rb +1 -1
- data/spec/mongo/monitoring/event/topology_opening_spec.rb +1 -1
- data/spec/mongo/operation/delete/op_msg_spec.rb +3 -3
- data/spec/mongo/operation/insert/command_spec.rb +2 -2
- data/spec/mongo/operation/insert/op_msg_spec.rb +3 -3
- data/spec/mongo/operation/read_preference_op_msg_spec.rb +1 -1
- data/spec/mongo/operation/update/command_spec.rb +2 -2
- data/spec/mongo/operation/update/op_msg_spec.rb +3 -3
- data/spec/mongo/protocol/msg_spec.rb +10 -0
- data/spec/mongo/query_cache_spec.rb +280 -0
- data/spec/mongo/semaphore_spec.rb +51 -0
- data/spec/mongo/server/app_metadata_shared.rb +82 -2
- data/spec/mongo/server/connection_auth_spec.rb +2 -2
- data/spec/mongo/server/connection_pool_spec.rb +7 -3
- data/spec/mongo/server/connection_spec.rb +15 -8
- data/spec/mongo/server/description_spec.rb +18 -0
- data/spec/mongo/server_selector/nearest_spec.rb +23 -23
- data/spec/mongo/server_selector/primary_preferred_spec.rb +26 -26
- data/spec/mongo/server_selector/primary_spec.rb +9 -9
- data/spec/mongo/server_selector/secondary_preferred_spec.rb +22 -22
- data/spec/mongo/server_selector/secondary_spec.rb +18 -18
- data/spec/mongo/server_selector_spec.rb +6 -6
- data/spec/mongo/session_spec.rb +35 -0
- data/spec/mongo/socket/ssl_spec.rb +4 -4
- data/spec/mongo/socket_spec.rb +1 -1
- data/spec/mongo/uri/srv_protocol_spec.rb +64 -33
- data/spec/mongo/uri_option_parsing_spec.rb +11 -11
- data/spec/mongo/uri_spec.rb +68 -41
- data/spec/mongo/utils_spec.rb +39 -0
- data/spec/runners/auth.rb +3 -0
- data/spec/runners/change_streams/test.rb +3 -3
- data/spec/runners/cmap.rb +1 -1
- data/spec/runners/command_monitoring.rb +3 -34
- data/spec/runners/connection_string.rb +35 -124
- data/spec/runners/crud/context.rb +9 -5
- data/spec/runners/crud/operation.rb +59 -27
- data/spec/runners/crud/spec.rb +0 -8
- data/spec/runners/crud/test.rb +1 -1
- data/spec/runners/crud/test_base.rb +0 -19
- data/spec/runners/sdam.rb +2 -2
- data/spec/runners/server_selection.rb +242 -28
- data/spec/runners/transactions.rb +12 -12
- data/spec/runners/transactions/operation.rb +151 -25
- data/spec/runners/transactions/test.rb +62 -18
- data/spec/shared/LICENSE +20 -0
- data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
- data/spec/shared/lib/mrss/constraints.rb +303 -0
- data/spec/shared/lib/mrss/lite_constraints.rb +175 -0
- data/spec/shared/lib/mrss/spec_organizer.rb +149 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/spec_tests/cmap_spec.rb +7 -3
- data/spec/spec_tests/command_monitoring_spec.rb +22 -12
- data/spec/spec_tests/crud_spec.rb +1 -1
- data/spec/spec_tests/data/change_streams/change-streams-errors.yml +4 -9
- data/spec/spec_tests/data/change_streams/change-streams-resume-whitelist.yml +66 -0
- data/spec/spec_tests/data/change_streams/change-streams.yml +0 -1
- data/spec/spec_tests/data/cmap/pool-checkout-connection.yml +6 -2
- data/spec/spec_tests/data/cmap/pool-create-min-size.yml +3 -0
- data/spec/spec_tests/data/connection_string/valid-warnings.yml +24 -0
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/MaxStalenessTooSmall.yml +15 -0
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/NoKnownServers.yml +4 -3
- data/spec/spec_tests/data/max_staleness/Unknown/SmallMaxStaleness.yml +1 -0
- data/spec/spec_tests/data/sdam_integration/cancel-server-check.yml +96 -0
- data/spec/spec_tests/data/sdam_integration/connectTimeoutMS.yml +88 -0
- data/spec/spec_tests/data/sdam_integration/find-network-error.yml +83 -0
- data/spec/spec_tests/data/sdam_integration/find-shutdown-error.yml +116 -0
- data/spec/spec_tests/data/sdam_integration/insert-network-error.yml +86 -0
- data/spec/spec_tests/data/sdam_integration/insert-shutdown-error.yml +115 -0
- data/spec/spec_tests/data/sdam_integration/isMaster-command-error.yml +168 -0
- data/spec/spec_tests/data/sdam_integration/isMaster-network-error.yml +162 -0
- data/spec/spec_tests/data/sdam_integration/isMaster-timeout.yml +229 -0
- data/spec/spec_tests/data/sdam_integration/rediscover-quickly-after-step-down.yml +87 -0
- data/spec/spec_tests/data/sdam_monitoring/discovered_standalone.yml +1 -3
- data/spec/spec_tests/data/sdam_monitoring/standalone.yml +2 -2
- data/spec/spec_tests/data/sdam_monitoring/standalone_repeated.yml +2 -2
- data/spec/spec_tests/data/sdam_monitoring/standalone_suppress_equal_description_changes.yml +2 -2
- data/spec/spec_tests/data/sdam_monitoring/standalone_to_rs_with_me_mismatch.yml +2 -2
- data/spec/spec_tests/data/uri_options/auth-options.yml +25 -0
- data/spec/spec_tests/data/uri_options/compression-options.yml +6 -3
- data/spec/spec_tests/data/uri_options/read-preference-options.yml +24 -0
- data/spec/spec_tests/data/uri_options/ruby-connection-options.yml +1 -0
- data/spec/spec_tests/data/uri_options/tls-options.yml +160 -4
- data/spec/spec_tests/dns_seedlist_discovery_spec.rb +9 -1
- data/spec/spec_tests/max_staleness_spec.rb +4 -142
- data/spec/spec_tests/retryable_reads_spec.rb +2 -2
- data/spec/spec_tests/sdam_integration_spec.rb +13 -0
- data/spec/spec_tests/sdam_monitoring_spec.rb +1 -2
- data/spec/spec_tests/server_selection_spec.rb +4 -116
- data/spec/spec_tests/uri_options_spec.rb +31 -33
- data/spec/stress/cleanup_spec.rb +17 -2
- data/spec/stress/connection_pool_stress_spec.rb +10 -8
- data/spec/stress/fork_reconnect_stress_spec.rb +1 -1
- data/spec/support/certificates/atlas-ocsp-ca.crt +28 -0
- data/spec/support/certificates/atlas-ocsp.crt +41 -0
- data/spec/support/client_registry.rb +1 -0
- data/spec/support/client_registry_macros.rb +11 -2
- data/spec/support/cluster_config.rb +4 -0
- data/spec/support/common_shortcuts.rb +45 -0
- data/spec/support/constraints.rb +6 -253
- data/spec/support/event_subscriber.rb +123 -33
- data/spec/support/keyword_struct.rb +26 -0
- data/spec/support/matchers.rb +16 -0
- data/spec/support/ocsp +1 -0
- data/spec/support/session_registry.rb +52 -0
- data/spec/support/shared/server_selector.rb +13 -1
- data/spec/support/spec_config.rb +60 -13
- data/spec/support/spec_setup.rb +1 -1
- data/spec/support/utils.rb +84 -1
- metadata +1027 -937
- metadata.gz.sig +0 -0
- data/lib/mongo/server_selector/selectable.rb +0 -560
- data/spec/runners/sdam_monitoring.rb +0 -89
- data/spec/support/lite_constraints.rb +0 -141
@@ -20,7 +20,6 @@ module Mongo
|
|
20
20
|
#
|
21
21
|
# @since 2.0.0
|
22
22
|
class Nearest < Base
|
23
|
-
include Selectable
|
24
23
|
|
25
24
|
# Name of the this read preference in the server's format.
|
26
25
|
#
|
@@ -92,14 +91,10 @@ module Mongo
|
|
92
91
|
# Select the near servers taking into account any defined tag sets and
|
93
92
|
# local threshold between the nearest server and other servers.
|
94
93
|
#
|
95
|
-
# @example Select nearest servers given a list of candidates.
|
96
|
-
# preference = Mongo::ServerSelector::Nearest.new
|
97
|
-
# preference.select_server(cluster)
|
98
|
-
#
|
99
94
|
# @return [ Array ] The nearest servers from the list of candidates.
|
100
95
|
#
|
101
96
|
# @since 2.0.0
|
102
|
-
def
|
97
|
+
def select_in_replica_set(candidates)
|
103
98
|
matching_servers = filter_stale_servers(candidates, primary(candidates).first)
|
104
99
|
matching_servers = match_tag_sets(matching_servers) unless tag_sets.empty?
|
105
100
|
near_servers(matching_servers)
|
@@ -21,7 +21,6 @@ module Mongo
|
|
21
21
|
#
|
22
22
|
# @since 2.0.0
|
23
23
|
class Primary < Base
|
24
|
-
include Selectable
|
25
24
|
|
26
25
|
# Name of the this read preference in the server's format.
|
27
26
|
#
|
@@ -94,14 +93,10 @@ module Mongo
|
|
94
93
|
|
95
94
|
# Select the primary server from a list of candidates.
|
96
95
|
#
|
97
|
-
# @example Select the primary server given a list of candidates.
|
98
|
-
# preference = Mongo::ServerSelector::Primary.new
|
99
|
-
# preference.select([candidate_1, candidate_2])
|
100
|
-
#
|
101
96
|
# @return [ Array ] The primary server from the list of candidates.
|
102
97
|
#
|
103
98
|
# @since 2.0.0
|
104
|
-
def
|
99
|
+
def select_in_replica_set(candidates)
|
105
100
|
primary(candidates)
|
106
101
|
end
|
107
102
|
|
@@ -21,7 +21,6 @@ module Mongo
|
|
21
21
|
#
|
22
22
|
# @since 2.0.0
|
23
23
|
class PrimaryPreferred < Base
|
24
|
-
include Selectable
|
25
24
|
|
26
25
|
# Name of the this read preference in the server's format.
|
27
26
|
#
|
@@ -93,19 +92,17 @@ module Mongo
|
|
93
92
|
# Select servers taking into account any defined tag sets and
|
94
93
|
# local threshold, with the primary preferred.
|
95
94
|
#
|
96
|
-
# @example Select servers given a list of candidates,
|
97
|
-
# with the primary preferred.
|
98
|
-
# preference = Mongo::ServerSelector::PrimaryPreferred.new
|
99
|
-
# preference.select([candidate_1, candidate_2])
|
100
|
-
#
|
101
95
|
# @return [ Array ] A list of servers matching tag sets and acceptable
|
102
96
|
# latency with the primary preferred.
|
103
97
|
#
|
104
98
|
# @since 2.0.0
|
105
|
-
def
|
106
|
-
|
107
|
-
|
108
|
-
|
99
|
+
def select_in_replica_set(candidates)
|
100
|
+
primaries = primary(candidates)
|
101
|
+
if primaries.first
|
102
|
+
primaries
|
103
|
+
else
|
104
|
+
near_servers(secondaries(candidates))
|
105
|
+
end
|
109
106
|
end
|
110
107
|
|
111
108
|
def max_staleness_allowed?
|
@@ -21,7 +21,6 @@ module Mongo
|
|
21
21
|
#
|
22
22
|
# @since 2.0.0
|
23
23
|
class Secondary < Base
|
24
|
-
include Selectable
|
25
24
|
|
26
25
|
# Name of the this read preference in the server's format.
|
27
26
|
#
|
@@ -93,14 +92,10 @@ module Mongo
|
|
93
92
|
# Select the secondary servers taking into account any defined tag sets and
|
94
93
|
# local threshold between the nearest secondary and other secondaries.
|
95
94
|
#
|
96
|
-
# @example Select secondary servers given a list of candidates.
|
97
|
-
# preference = Mongo::ServerSelector::Secondary.new
|
98
|
-
# preference.select([candidate_1, candidate_2])
|
99
|
-
#
|
100
95
|
# @return [ Array ] The secondary servers from the list of candidates.
|
101
96
|
#
|
102
97
|
# @since 2.0.0
|
103
|
-
def
|
98
|
+
def select_in_replica_set(candidates)
|
104
99
|
near_servers(secondaries(candidates))
|
105
100
|
end
|
106
101
|
|
@@ -21,7 +21,6 @@ module Mongo
|
|
21
21
|
#
|
22
22
|
# @since 2.0.0
|
23
23
|
class SecondaryPreferred < Base
|
24
|
-
include Selectable
|
25
24
|
|
26
25
|
# Name of the this read preference in the server's format.
|
27
26
|
#
|
@@ -101,16 +100,11 @@ module Mongo
|
|
101
100
|
# Select servers taking into account any defined tag sets and
|
102
101
|
# local threshold, with secondaries.
|
103
102
|
#
|
104
|
-
# @example Select servers given a list of candidates,
|
105
|
-
# with secondaries preferred.
|
106
|
-
# preference = Mongo::ServerSelector::SecondaryPreferred.new
|
107
|
-
# preference.select([candidate_1, candidate_2])
|
108
|
-
#
|
109
103
|
# @return [ Array ] A list of servers matching tag sets and acceptable
|
110
104
|
# latency with secondaries preferred.
|
111
105
|
#
|
112
106
|
# @since 2.0.0
|
113
|
-
def
|
107
|
+
def select_in_replica_set(candidates)
|
114
108
|
near_servers(secondaries(candidates)) + primary(candidates)
|
115
109
|
end
|
116
110
|
|
data/lib/mongo/session.rb
CHANGED
@@ -227,7 +227,9 @@ module Mongo
|
|
227
227
|
# Error message describing that sessions are not supported by the server version.
|
228
228
|
#
|
229
229
|
# @since 2.5.0
|
230
|
+
# @deprecated
|
230
231
|
SESSIONS_NOT_SUPPORTED = 'Sessions are not supported by the connected servers.'.freeze
|
232
|
+
# Note: SESSIONS_NOT_SUPPORTED is used by Mongoid - do not remove from driver.
|
231
233
|
|
232
234
|
# The state of a session in which the last operation was not related to
|
233
235
|
# any transaction or no operations have yet occurred.
|
@@ -377,6 +379,7 @@ module Mongo
|
|
377
379
|
rv = yield self
|
378
380
|
rescue Exception => e
|
379
381
|
if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
|
382
|
+
log_warn("Aborting transaction due to #{e.class}: #{e}")
|
380
383
|
abort_transaction
|
381
384
|
transaction_in_progress = false
|
382
385
|
end
|
@@ -441,7 +444,7 @@ module Mongo
|
|
441
444
|
true
|
442
445
|
ensure
|
443
446
|
if transaction_in_progress
|
444
|
-
log_warn('with_transaction callback
|
447
|
+
log_warn('with_transaction callback broke out of with_transaction loop, aborting transaction')
|
445
448
|
begin
|
446
449
|
abort_transaction
|
447
450
|
rescue Error::OperationFailure, Error::InvalidTransactionOperation
|
@@ -532,6 +535,7 @@ module Mongo
|
|
532
535
|
#
|
533
536
|
# @since 2.6.0
|
534
537
|
def commit_transaction(options=nil)
|
538
|
+
QueryCache.clear
|
535
539
|
check_if_ended!
|
536
540
|
check_if_no_transaction!
|
537
541
|
|
@@ -600,6 +604,8 @@ module Mongo
|
|
600
604
|
#
|
601
605
|
# @since 2.6.0
|
602
606
|
def abort_transaction
|
607
|
+
QueryCache.clear
|
608
|
+
|
603
609
|
check_if_ended!
|
604
610
|
check_if_no_transaction!
|
605
611
|
|
data/lib/mongo/socket.rb
CHANGED
@@ -15,19 +15,22 @@
|
|
15
15
|
require 'mongo/socket/ssl'
|
16
16
|
require 'mongo/socket/tcp'
|
17
17
|
require 'mongo/socket/unix'
|
18
|
+
require 'mongo/socket/ocsp_verifier'
|
19
|
+
require 'mongo/socket/ocsp_cache'
|
18
20
|
|
19
21
|
module Mongo
|
20
22
|
|
21
23
|
# Provides additional data around sockets for the driver's use.
|
22
24
|
#
|
23
25
|
# @since 2.0.0
|
26
|
+
# @api private
|
24
27
|
class Socket
|
25
28
|
include ::Socket::Constants
|
26
29
|
|
27
|
-
# Error message for
|
30
|
+
# Error message for TLS related exceptions.
|
28
31
|
#
|
29
32
|
# @since 2.0.0
|
30
|
-
SSL_ERROR = 'MongoDB may not be configured with
|
33
|
+
SSL_ERROR = 'MongoDB may not be configured with TLS support'.freeze
|
31
34
|
|
32
35
|
# Error message for timeouts on socket calls.
|
33
36
|
#
|
@@ -105,9 +108,14 @@ module Mongo
|
|
105
108
|
def summary
|
106
109
|
fileno = @socket&.fileno rescue '<no socket>' || '<no socket>'
|
107
110
|
if monitor?
|
108
|
-
|
111
|
+
indicator = if options[:push]
|
112
|
+
'pm'
|
113
|
+
else
|
114
|
+
'm'
|
115
|
+
end
|
116
|
+
"#{connection_address};#{indicator};fd=#{fileno}"
|
109
117
|
else
|
110
|
-
"#{connection_address}
|
118
|
+
"#{connection_address};c:#{connection_generation};fd=#{fileno}"
|
111
119
|
end
|
112
120
|
end
|
113
121
|
|
@@ -123,7 +131,7 @@ module Mongo
|
|
123
131
|
sock_arr = [ @socket ]
|
124
132
|
if Kernel::select(sock_arr, nil, sock_arr, 0)
|
125
133
|
# The eof? call is supposed to return immediately since select
|
126
|
-
# indicated the socket is readable. However, if @socket is
|
134
|
+
# indicated the socket is readable. However, if @socket is a TLS
|
127
135
|
# socket, eof? can block anyway - see RUBY-2140.
|
128
136
|
begin
|
129
137
|
Timeout.timeout(0.1) do
|
@@ -173,20 +181,21 @@ module Mongo
|
|
173
181
|
# socket.read(4096)
|
174
182
|
#
|
175
183
|
# @param [ Integer ] length The number of bytes to read.
|
184
|
+
# @param [ Numeric ] timeout The timeout to use for each chunk read.
|
176
185
|
#
|
177
186
|
# @raise [ Mongo::SocketError ] If not all data is returned.
|
178
187
|
#
|
179
188
|
# @return [ Object ] The data from the socket.
|
180
189
|
#
|
181
190
|
# @since 2.0.0
|
182
|
-
def read(length)
|
191
|
+
def read(length, timeout: nil)
|
183
192
|
map_exceptions do
|
184
|
-
data = read_from_socket(length)
|
193
|
+
data = read_from_socket(length, timeout: timeout)
|
185
194
|
unless (data.length > 0 || length == 0)
|
186
195
|
raise IOError, "Expected to read > 0 bytes but read 0 bytes"
|
187
196
|
end
|
188
197
|
while data.length < length
|
189
|
-
chunk = read_from_socket(length - data.length)
|
198
|
+
chunk = read_from_socket(length - data.length, timeout: timeout)
|
190
199
|
unless (chunk.length > 0 || length == 0)
|
191
200
|
raise IOError, "Expected to read > 0 bytes but read 0 bytes"
|
192
201
|
end
|
@@ -243,14 +252,19 @@ module Mongo
|
|
243
252
|
|
244
253
|
private
|
245
254
|
|
246
|
-
def read_from_socket(length)
|
255
|
+
def read_from_socket(length, timeout: nil)
|
247
256
|
# Just in case
|
248
257
|
if length == 0
|
249
258
|
return ''.force_encoding('BINARY')
|
250
259
|
end
|
251
260
|
|
252
|
-
|
253
|
-
|
261
|
+
_timeout = timeout || self.timeout
|
262
|
+
if _timeout
|
263
|
+
if _timeout > 0
|
264
|
+
deadline = Time.now + _timeout
|
265
|
+
elsif _timeout < 0
|
266
|
+
raise Errno::ETIMEDOUT, "Negative timeout #{_timeout} given to socket"
|
267
|
+
end
|
254
268
|
end
|
255
269
|
|
256
270
|
# We want to have a fixed and reasonably small size buffer for reads
|
@@ -330,7 +344,7 @@ module Mongo
|
|
330
344
|
end
|
331
345
|
|
332
346
|
def read_buffer_size
|
333
|
-
# Buffer size for non-
|
347
|
+
# Buffer size for non-TLS reads
|
334
348
|
# 64kb
|
335
349
|
65536
|
336
350
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# Copyright (C) 2020 MongoDB Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module Mongo
|
16
|
+
class Socket
|
17
|
+
|
18
|
+
# This module caches OCSP responses for their indicated validity time.
|
19
|
+
#
|
20
|
+
# The key is the CertificateId used for the OCSP request.
|
21
|
+
# The value is the SingleResponse on Ruby 2.4+, or the OpenStruct
|
22
|
+
# emulation of it on Ruby 2.3.
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
module OcspCache
|
26
|
+
module_function def set(cert_id, response)
|
27
|
+
delete(cert_id)
|
28
|
+
responses << response
|
29
|
+
end
|
30
|
+
|
31
|
+
# Retrieves a cached SingleResponse for the specified CertificateId.
|
32
|
+
#
|
33
|
+
# This method may return expired responses if they are revoked.
|
34
|
+
# Such responses were valid when they were first received.
|
35
|
+
#
|
36
|
+
# This method may also return responses that are valid but that may
|
37
|
+
# expire by the time caller uses them. The caller should not perform
|
38
|
+
# update time checks on the returned response.
|
39
|
+
#
|
40
|
+
# @return [ OpenSSL::OCSP::SingleResponse | OpenStruct ] The previously
|
41
|
+
# retrieved response.
|
42
|
+
module_function def get(cert_id)
|
43
|
+
resp = responses.detect do |resp|
|
44
|
+
resp.certid.cmp(cert_id)
|
45
|
+
end
|
46
|
+
if resp
|
47
|
+
# Only expire responses with good status.
|
48
|
+
# Once a certificate is revoked, it should stay revoked forever,
|
49
|
+
# hence we should be able to cache revoked responses indefinitely.
|
50
|
+
if resp.cert_status == OpenSSL::OCSP::V_CERTSTATUS_GOOD &&
|
51
|
+
resp.next_update < Time.now
|
52
|
+
then
|
53
|
+
responses.delete(resp)
|
54
|
+
resp = nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# If we have connected to a server and cached the OCSP response for it,
|
59
|
+
# and then never connect to that server again, the cached OCSP response
|
60
|
+
# is going to remain in memory indefinitely. Periodically remove all
|
61
|
+
# expired OCSP responses, not just the ones matching the certificate id
|
62
|
+
# we are querying by.
|
63
|
+
if rand < 0.01
|
64
|
+
responses.delete_if do |resp|
|
65
|
+
resp.next_update < Time.now
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
resp
|
70
|
+
end
|
71
|
+
|
72
|
+
module_function def delete(cert_id)
|
73
|
+
responses.delete_if do |resp|
|
74
|
+
resp.certid.cmp(cert_id)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Clears the driver's OCSP response cache.
|
79
|
+
#
|
80
|
+
# @note Use Mongo.clear_ocsp_cache from applications instead of invoking
|
81
|
+
# this method directly.
|
82
|
+
module_function def clear
|
83
|
+
responses.replace([])
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
LOCK = Mutex.new
|
89
|
+
|
90
|
+
module_function def responses
|
91
|
+
LOCK.synchronize do
|
92
|
+
@responses ||= []
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,368 @@
|
|
1
|
+
# Copyright (C) 2020 MongoDB Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module Net
|
16
|
+
autoload :HTTP, 'net/http'
|
17
|
+
end
|
18
|
+
|
19
|
+
module Mongo
|
20
|
+
class Socket
|
21
|
+
|
22
|
+
# OCSP endpoint verifier.
|
23
|
+
#
|
24
|
+
# After a TLS connection is established, this verifier inspects the
|
25
|
+
# certificate presented by the server, and if the certificate contains
|
26
|
+
# an OCSP URI, performs the OCSP status request to the specified URI
|
27
|
+
# (following up to 5 redirects) to verify the certificate status.
|
28
|
+
#
|
29
|
+
# @see https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/OCSP.html
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
class OcspVerifier
|
33
|
+
include Loggable
|
34
|
+
|
35
|
+
# @param [ String ] host_name The host name being verified, for
|
36
|
+
# diagnostic output.
|
37
|
+
# @param [ OpenSSL::X509::Certificate ] cert The certificate presented by
|
38
|
+
# the server at host_name.
|
39
|
+
# @param [ OpenSSL::X509::Certificate ] ca_cert The CA certificate
|
40
|
+
# presented by the server or resolved locally from the server
|
41
|
+
# certificate.
|
42
|
+
# @param [ OpenSSL::X509::Store ] cert_store The certificate store to
|
43
|
+
# use for verifying OCSP response. This should be the same store as
|
44
|
+
# used in SSLContext used with the SSLSocket that we are verifying the
|
45
|
+
# certificate for. This must NOT be the CA certificate provided by
|
46
|
+
# the server (i.e. anything taken out of peer_cert) - otherwise the
|
47
|
+
# server would dictate which CA authorities the client trusts.
|
48
|
+
def initialize(host_name, cert, ca_cert, cert_store, **opts)
|
49
|
+
@host_name = host_name
|
50
|
+
@cert = cert
|
51
|
+
@ca_cert = ca_cert
|
52
|
+
@cert_store = cert_store
|
53
|
+
@options = opts
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :host_name
|
57
|
+
attr_reader :cert
|
58
|
+
attr_reader :ca_cert
|
59
|
+
attr_reader :cert_store
|
60
|
+
attr_reader :options
|
61
|
+
|
62
|
+
def timeout
|
63
|
+
options[:timeout] || 5
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [ Array<String> ] OCSP URIs in the specified server certificate.
|
67
|
+
def ocsp_uris
|
68
|
+
@ocsp_uris ||= begin
|
69
|
+
# https://tools.ietf.org/html/rfc3546#section-2.3
|
70
|
+
# prohibits multiple extensions with the same oid.
|
71
|
+
ext = cert.extensions.detect do |ext|
|
72
|
+
ext.oid == 'authorityInfoAccess'
|
73
|
+
end
|
74
|
+
|
75
|
+
if ext
|
76
|
+
# Our test certificates have multiple OCSP URIs.
|
77
|
+
ext.value.split("\n").select do |line|
|
78
|
+
line.start_with?('OCSP - URI:')
|
79
|
+
end.map do |line|
|
80
|
+
line.split(':', 2).last
|
81
|
+
end
|
82
|
+
else
|
83
|
+
[]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def cert_id
|
89
|
+
@cert_id ||= OpenSSL::OCSP::CertificateId.new(
|
90
|
+
cert,
|
91
|
+
ca_cert,
|
92
|
+
OpenSSL::Digest::SHA1.new,
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def verify_with_cache
|
97
|
+
handle_exceptions do
|
98
|
+
return false if ocsp_uris.empty?
|
99
|
+
|
100
|
+
resp = OcspCache.get(cert_id)
|
101
|
+
if resp
|
102
|
+
return return_ocsp_response(resp)
|
103
|
+
end
|
104
|
+
|
105
|
+
resp, errors = do_verify
|
106
|
+
|
107
|
+
if resp
|
108
|
+
OcspCache.set(cert_id, resp)
|
109
|
+
end
|
110
|
+
|
111
|
+
return_ocsp_response(resp, errors)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [ true | false ] Whether the certificate was verified.
|
116
|
+
#
|
117
|
+
# @raise [ Error::ServerCertificateRevoked ] If the certificate was
|
118
|
+
# definitively revoked.
|
119
|
+
def verify
|
120
|
+
handle_exceptions do
|
121
|
+
return false if ocsp_uris.empty?
|
122
|
+
|
123
|
+
resp, errors = do_verify
|
124
|
+
return_ocsp_response(resp, errors)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def do_verify
|
131
|
+
# This synchronized array contains definitive pass/fail responses
|
132
|
+
# obtained from the responders. We'll take the first one but due to
|
133
|
+
# concurrency multiple responses may be produced and queued.
|
134
|
+
@resp_queue = Queue.new
|
135
|
+
|
136
|
+
# This synchronized array contains strings, one per responder, that
|
137
|
+
# explain why each responder hasn't produced a definitive response.
|
138
|
+
# These are concatenated and logged if none of the responders produced
|
139
|
+
# a definitive respnose, or if the main thread times out waiting for
|
140
|
+
# a definitive response (in which case some of the worker threads'
|
141
|
+
# diagnostics may be logged and some may not).
|
142
|
+
@resp_errors = Queue.new
|
143
|
+
|
144
|
+
@req = OpenSSL::OCSP::Request.new
|
145
|
+
@req.add_certid(cert_id)
|
146
|
+
@req.add_nonce
|
147
|
+
@serialized_req = @req.to_der
|
148
|
+
|
149
|
+
@outstanding_requests = ocsp_uris.count
|
150
|
+
@outstanding_requests_lock = Mutex.new
|
151
|
+
|
152
|
+
threads = ocsp_uris.map do |uri|
|
153
|
+
Thread.new do
|
154
|
+
verify_one_responder(uri)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
resp = begin
|
159
|
+
::Timeout.timeout(timeout) do
|
160
|
+
@resp_queue.shift
|
161
|
+
end
|
162
|
+
rescue ::Timeout::Error
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
|
166
|
+
threads.map(&:kill)
|
167
|
+
threads.map(&:join)
|
168
|
+
|
169
|
+
[resp, @resp_errors]
|
170
|
+
end
|
171
|
+
|
172
|
+
def verify_one_responder(uri)
|
173
|
+
original_uri = uri
|
174
|
+
redirect_count = 0
|
175
|
+
http_response = nil
|
176
|
+
loop do
|
177
|
+
http_response = begin
|
178
|
+
uri = URI(uri)
|
179
|
+
Net::HTTP.start(uri.hostname, uri.port) do |http|
|
180
|
+
path = uri.path
|
181
|
+
if path.empty?
|
182
|
+
path = '/'
|
183
|
+
end
|
184
|
+
http.post(path, @serialized_req,
|
185
|
+
'content-type' => 'application/ocsp-request')
|
186
|
+
end
|
187
|
+
rescue IOError, SystemCallError => e
|
188
|
+
@resp_errors << "OCSP request to #{report_uri(original_uri, uri)} failed: #{e.class}: #{e}"
|
189
|
+
return false
|
190
|
+
end
|
191
|
+
|
192
|
+
code = http_response.code.to_i
|
193
|
+
if (300..399).include?(code)
|
194
|
+
redirected_uri = http_response.header['location']
|
195
|
+
uri = ::URI.join(uri, redirected_uri)
|
196
|
+
redirect_count += 1
|
197
|
+
if redirect_count > 5
|
198
|
+
@resp_errors << "OCSP request to #{report_uri(original_uri, uri)} failed: too many redirects (6)"
|
199
|
+
return false
|
200
|
+
end
|
201
|
+
next
|
202
|
+
end
|
203
|
+
|
204
|
+
if code >= 400
|
205
|
+
@resp_errors << "OCSP request to #{report_uri(original_uri, uri)} failed with HTTP status code #{http_response.code}" + report_response_body(http_response.body)
|
206
|
+
return false
|
207
|
+
end
|
208
|
+
|
209
|
+
if code != 200
|
210
|
+
# There must be a body provided with the response, if one isn't
|
211
|
+
# provided the response cannot be verified.
|
212
|
+
@resp_errors << "OCSP request to #{report_uri(original_uri, uri)} failed with unexpected HTTP status code #{http_response.code}" + report_response_body(http_response.body)
|
213
|
+
return false
|
214
|
+
end
|
215
|
+
|
216
|
+
break
|
217
|
+
end
|
218
|
+
|
219
|
+
resp = OpenSSL::OCSP::Response.new(http_response.body).basic
|
220
|
+
unless resp.verify([ca_cert], cert_store)
|
221
|
+
# Ruby's OpenSSL binding discards error information - see
|
222
|
+
# https://github.com/ruby/openssl/issues/395
|
223
|
+
@resp_errors << "OCSP response from #{report_uri(original_uri, uri)} failed signature verification; set `OpenSSL.debug = true` to see why"
|
224
|
+
return false
|
225
|
+
end
|
226
|
+
|
227
|
+
if @req.check_nonce(resp) == 0
|
228
|
+
@resp_errors << "OCSP response from #{report_uri(original_uri, uri)} included invalid nonce"
|
229
|
+
return false
|
230
|
+
end
|
231
|
+
|
232
|
+
if resp.respond_to?(:find_response)
|
233
|
+
# Ruby 2.4+
|
234
|
+
resp = resp.find_response(cert_id)
|
235
|
+
# TODO make a new class instead of patching the stdlib one?
|
236
|
+
resp.instance_variable_set('@uri', uri)
|
237
|
+
resp.instance_variable_set('@original_uri', original_uri)
|
238
|
+
class << resp
|
239
|
+
attr_reader :uri, :original_uri
|
240
|
+
end
|
241
|
+
else
|
242
|
+
# Ruby 2.3
|
243
|
+
found = nil
|
244
|
+
resp.status.each do |_cert_id, cert_status, revocation_reason, revocation_time, this_update, next_update, extensions|
|
245
|
+
if _cert_id.cmp(cert_id)
|
246
|
+
found = OpenStruct.new(
|
247
|
+
cert_status: cert_status,
|
248
|
+
certid: _cert_id,
|
249
|
+
next_update: next_update,
|
250
|
+
this_update: this_update,
|
251
|
+
revocation_reason: revocation_reason,
|
252
|
+
revocation_time: revocation_time,
|
253
|
+
extensions: extensions,
|
254
|
+
uri: uri,
|
255
|
+
original_uri: original_uri,
|
256
|
+
)
|
257
|
+
class << found
|
258
|
+
# Unlike the stdlib method, this one doesn't accept
|
259
|
+
# any arguments.
|
260
|
+
def check_validity
|
261
|
+
now = Time.now
|
262
|
+
this_update <= now && next_update >= now
|
263
|
+
end
|
264
|
+
end
|
265
|
+
break
|
266
|
+
end
|
267
|
+
end
|
268
|
+
resp = found
|
269
|
+
end
|
270
|
+
|
271
|
+
unless resp
|
272
|
+
@resp_errors << "OCSP response from #{report_uri(original_uri, uri)} did not include information about the requested certificate"
|
273
|
+
return false
|
274
|
+
end
|
275
|
+
|
276
|
+
unless resp.check_validity
|
277
|
+
@resp_errors << "OCSP response from #{report_uri(original_uri, uri)} was invalid: this_update was in the future or next_update time has passed"
|
278
|
+
return false
|
279
|
+
end
|
280
|
+
|
281
|
+
unless [
|
282
|
+
OpenSSL::OCSP::V_CERTSTATUS_GOOD,
|
283
|
+
OpenSSL::OCSP::V_CERTSTATUS_REVOKED,
|
284
|
+
].include?(resp.cert_status)
|
285
|
+
@resp_errors << "OCSP response from #{report_uri(original_uri, uri)} had a non-definitive status: #{resp.cert_status}"
|
286
|
+
return false
|
287
|
+
end
|
288
|
+
|
289
|
+
# Note this returns the redirected URI
|
290
|
+
@resp_queue << resp
|
291
|
+
rescue => exc
|
292
|
+
Utils.warn_bg_exception("Error performing OCSP verification for '#{host_name}' via '#{uri}'", exc,
|
293
|
+
logger: options[:logger],
|
294
|
+
log_prefix: options[:log_prefix],
|
295
|
+
bg_error_backtrace: options[:bg_error_backtrace],
|
296
|
+
)
|
297
|
+
false
|
298
|
+
ensure
|
299
|
+
@outstanding_requests_lock.synchronize do
|
300
|
+
@outstanding_requests -= 1
|
301
|
+
if @outstanding_requests == 0
|
302
|
+
@resp_queue << nil
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def return_ocsp_response(resp, errors = nil)
|
308
|
+
if resp
|
309
|
+
if resp.cert_status == OpenSSL::OCSP::V_CERTSTATUS_REVOKED
|
310
|
+
raise_revoked_error(resp)
|
311
|
+
end
|
312
|
+
true
|
313
|
+
else
|
314
|
+
reasons = []
|
315
|
+
errors.length.times do
|
316
|
+
reasons << errors.shift
|
317
|
+
end
|
318
|
+
if reasons.empty?
|
319
|
+
msg = "No responses from responders: #{ocsp_uris.join(', ')} within #{timeout} seconds"
|
320
|
+
else
|
321
|
+
msg = "For responders #{ocsp_uris.join(', ')} with a timeout of #{timeout} seconds: #{reasons.join(', ')}"
|
322
|
+
end
|
323
|
+
log_warn("TLS certificate of '#{host_name}' could not be definitively verified via OCSP: #{msg}")
|
324
|
+
false
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def handle_exceptions
|
329
|
+
begin
|
330
|
+
yield
|
331
|
+
rescue Error::ServerCertificateRevoked
|
332
|
+
raise
|
333
|
+
rescue => exc
|
334
|
+
Utils.warn_bg_exception(
|
335
|
+
"Error performing OCSP verification for '#{host_name}'",
|
336
|
+
exc,
|
337
|
+
**options)
|
338
|
+
false
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def raise_revoked_error(resp)
|
343
|
+
if resp.uri == resp.original_uri
|
344
|
+
redirect = ''
|
345
|
+
else
|
346
|
+
redirect = " (redirected from #{resp.original_uri})"
|
347
|
+
end
|
348
|
+
raise Error::ServerCertificateRevoked, "TLS certificate of '#{host_name}' has been revoked according to '#{resp.uri}'#{redirect} for reason '#{resp.revocation_reason}' at '#{resp.revocation_time}'"
|
349
|
+
end
|
350
|
+
|
351
|
+
def report_uri(original_uri, uri)
|
352
|
+
if URI(uri) == URI(original_uri)
|
353
|
+
uri
|
354
|
+
else
|
355
|
+
"#{original_uri} (redirected to #{uri})"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def report_response_body(body)
|
360
|
+
if body
|
361
|
+
": #{body}"
|
362
|
+
else
|
363
|
+
''
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|