mongo 2.13.0 → 2.15.0.alpha
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 +1 -4
- data.tar.gz.sig +0 -0
- data/README.md +4 -1
- data/Rakefile +46 -18
- data/lib/mongo.rb +32 -0
- 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/conversation.rb +1 -4
- data/lib/mongo/auth/base.rb +13 -7
- data/lib/mongo/auth/conversation_base.rb +32 -0
- data/lib/mongo/auth/cr/conversation.rb +6 -29
- data/lib/mongo/auth/gssapi/conversation.rb +4 -15
- data/lib/mongo/auth/ldap/conversation.rb +3 -14
- data/lib/mongo/auth/sasl_conversation_base.rb +1 -13
- data/lib/mongo/auth/scram_conversation_base.rb +7 -34
- data/lib/mongo/auth/user/view.rb +16 -9
- data/lib/mongo/auth/x509/conversation.rb +4 -25
- data/lib/mongo/background_thread.rb +11 -0
- data/lib/mongo/bulk_write.rb +38 -18
- data/lib/mongo/caching_cursor.rb +74 -0
- data/lib/mongo/client.rb +142 -16
- data/lib/mongo/cluster.rb +22 -31
- data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -2
- data/lib/mongo/cluster/sdam_flow.rb +14 -0
- data/lib/mongo/cluster/topology/single.rb +1 -1
- data/lib/mongo/collection.rb +58 -18
- data/lib/mongo/collection/view.rb +24 -20
- data/lib/mongo/collection/view/aggregation.rb +26 -5
- data/lib/mongo/collection/view/builder/find_command.rb +38 -18
- data/lib/mongo/collection/view/change_stream.rb +1 -1
- data/lib/mongo/collection/view/explainable.rb +27 -8
- data/lib/mongo/collection/view/iterable.rb +73 -13
- data/lib/mongo/collection/view/map_reduce.rb +2 -2
- data/lib/mongo/collection/view/readable.rb +57 -21
- data/lib/mongo/collection/view/writable.rb +29 -15
- data/lib/mongo/crypt/encryption_io.rb +6 -6
- data/lib/mongo/cursor.rb +18 -5
- data/lib/mongo/database.rb +28 -5
- data/lib/mongo/database/view.rb +2 -2
- data/lib/mongo/error.rb +11 -1
- data/lib/mongo/error/bulk_write_error.rb +17 -3
- data/lib/mongo/error/internal_driver_error.rb +22 -0
- data/lib/mongo/error/invalid_read_concern.rb +28 -0
- data/lib/mongo/error/operation_failure.rb +26 -7
- data/lib/mongo/error/parser.rb +65 -12
- data/lib/mongo/error/server_api_conflict.rb +23 -0
- data/lib/mongo/error/server_api_not_supported.rb +24 -0
- data/lib/mongo/error/server_certificate_revoked.rb +22 -0
- data/lib/mongo/error/unmet_dependency.rb +21 -0
- data/lib/mongo/error/unsupported_option.rb +14 -12
- data/lib/mongo/grid/fs_bucket.rb +37 -37
- data/lib/mongo/index/view.rb +21 -11
- data/lib/mongo/lint.rb +2 -1
- data/lib/mongo/logger.rb +3 -3
- data/lib/mongo/monitoring.rb +13 -4
- data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +27 -16
- data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +26 -15
- data/lib/mongo/operation.rb +4 -2
- data/lib/mongo/operation/aggregate/result.rb +9 -8
- data/lib/mongo/operation/collections_info.rb +18 -1
- data/lib/mongo/operation/collections_info/command.rb +5 -0
- data/lib/mongo/operation/collections_info/result.rb +18 -1
- data/lib/mongo/operation/context.rb +99 -0
- 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.rb +15 -1
- data/lib/mongo/operation/indexes/result.rb +5 -0
- data/lib/mongo/operation/insert/bulk_result.rb +5 -0
- data/lib/mongo/operation/insert/command.rb +2 -2
- data/lib/mongo/operation/insert/legacy.rb +2 -2
- data/lib/mongo/operation/insert/op_msg.rb +2 -2
- data/lib/mongo/operation/insert/result.rb +5 -0
- data/lib/mongo/operation/list_collections/result.rb +9 -1
- 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 +37 -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/executable.rb +25 -14
- data/lib/mongo/operation/shared/executable_no_validate.rb +2 -2
- 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/op_msg_or_command.rb +1 -7
- data/lib/mongo/operation/shared/op_msg_or_find_command.rb +1 -7
- data/lib/mongo/operation/shared/polymorphic_operation.rb +39 -0
- data/lib/mongo/operation/shared/response_handling.rb +23 -23
- data/lib/mongo/operation/shared/result/aggregatable.rb +1 -0
- data/lib/mongo/operation/shared/sessions_supported.rb +14 -2
- data/lib/mongo/operation/shared/specifiable.rb +1 -0
- data/lib/mongo/operation/shared/write.rb +9 -18
- 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/compressed.rb +51 -5
- data/lib/mongo/protocol/message.rb +31 -4
- data/lib/mongo/protocol/msg.rb +37 -12
- data/lib/mongo/protocol/query.rb +36 -0
- data/lib/mongo/query_cache.rb +272 -0
- data/lib/mongo/retryable.rb +9 -2
- data/lib/mongo/server.rb +12 -16
- data/lib/mongo/server/app_metadata.rb +52 -18
- data/lib/mongo/server/connection.rb +5 -0
- data/lib/mongo/server/connection_base.rb +16 -15
- data/lib/mongo/server/connection_common.rb +2 -2
- data/lib/mongo/server/connection_pool.rb +9 -4
- data/lib/mongo/server/description.rb +12 -1
- data/lib/mongo/server/description/features.rb +9 -8
- data/lib/mongo/server/monitor.rb +21 -2
- data/lib/mongo/server/monitor/app_metadata.rb +1 -1
- data/lib/mongo/server/monitor/connection.rb +12 -13
- data/lib/mongo/server/pending_connection.rb +26 -8
- data/lib/mongo/server/push_monitor.rb +12 -2
- data/lib/mongo/server_selector/base.rb +5 -1
- data/lib/mongo/session.rb +7 -3
- data/lib/mongo/session/session_pool.rb +4 -2
- data/lib/mongo/socket.rb +35 -8
- data/lib/mongo/socket/ocsp_cache.rb +97 -0
- data/lib/mongo/socket/ocsp_verifier.rb +368 -0
- data/lib/mongo/socket/ssl.rb +53 -24
- data/lib/mongo/srv/monitor.rb +7 -24
- data/lib/mongo/srv/resolver.rb +14 -10
- data/lib/mongo/timeout.rb +2 -0
- data/lib/mongo/uri.rb +21 -390
- data/lib/mongo/uri/options_mapper.rb +620 -0
- data/lib/mongo/uri/srv_protocol.rb +3 -2
- data/lib/mongo/utils.rb +27 -1
- data/lib/mongo/version.rb +1 -1
- data/spec/NOTES.aws-auth.md +12 -7
- data/spec/README.md +87 -2
- data/spec/integration/auth_spec.rb +25 -15
- data/spec/integration/bson_symbol_spec.rb +4 -2
- data/spec/integration/bulk_write_error_message_spec.rb +41 -0
- data/spec/integration/bulk_write_spec.rb +48 -0
- data/spec/integration/change_stream_spec.rb +5 -5
- data/spec/integration/client_authentication_options_spec.rb +92 -28
- data/spec/integration/client_side_encryption/auto_encryption_bulk_writes_spec.rb +6 -2
- data/spec/integration/command_monitoring_spec.rb +2 -2
- data/spec/integration/connection_pool_populator_spec.rb +4 -2
- data/spec/integration/connection_spec.rb +2 -0
- data/spec/integration/cursor_reaping_spec.rb +54 -18
- data/spec/integration/docs_examples_spec.rb +8 -1
- data/spec/integration/fork_reconnect_spec.rb +60 -2
- 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 +340 -0
- data/spec/integration/operation_failure_code_spec.rb +1 -1
- data/spec/integration/operation_failure_message_spec.rb +90 -0
- data/spec/integration/query_cache_spec.rb +1045 -0
- data/spec/integration/query_cache_transactions_spec.rb +190 -0
- data/spec/integration/reconnect_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 +2 -0
- data/spec/integration/sdam_error_handling_spec.rb +86 -1
- data/spec/integration/sdam_events_spec.rb +8 -7
- data/spec/integration/server_selection_spec.rb +36 -0
- data/spec/integration/size_limit_spec.rb +20 -19
- data/spec/integration/snappy_compression_spec.rb +25 -0
- data/spec/integration/srv_monitoring_spec.rb +39 -4
- data/spec/integration/srv_spec.rb +56 -0
- data/spec/integration/transactions_examples_spec.rb +23 -7
- data/spec/integration/zlib_compression_spec.rb +1 -1
- data/spec/integration/zstd_compression_spec.rb +26 -0
- data/spec/lite_spec_helper.rb +15 -5
- data/spec/mongo/address_spec.rb +16 -12
- data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
- data/spec/mongo/auth/ldap_spec.rb +5 -1
- data/spec/mongo/auth/scram_negotiation_spec.rb +1 -1
- data/spec/mongo/auth/scram_spec.rb +1 -1
- data/spec/mongo/auth/user_spec.rb +1 -1
- data/spec/mongo/auth/x509/conversation_spec.rb +3 -3
- 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 +273 -35
- data/spec/mongo/client_encryption_spec.rb +16 -10
- data/spec/mongo/client_spec.rb +64 -0
- data/spec/mongo/cluster/topology/replica_set_spec.rb +1 -1
- data/spec/mongo/cluster/topology/sharded_spec.rb +1 -1
- data/spec/mongo/cluster/topology/single_spec.rb +15 -6
- 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 +6 -18
- data/spec/mongo/collection/view/change_stream_resume_spec.rb +1 -1
- 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 +50 -0
- data/spec/mongo/collection_crud_spec.rb +4357 -0
- data/spec/mongo/collection_ddl_spec.rb +534 -0
- data/spec/mongo/collection_spec.rb +5 -4787
- 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 +174 -4
- data/spec/mongo/error/bulk_write_error_spec.rb +3 -3
- data/spec/mongo/error/no_server_available_spec.rb +1 -1
- data/spec/mongo/error/parser_spec.rb +37 -6
- data/spec/mongo/index/view_spec.rb +8 -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_heartbeat_failed_spec.rb +1 -1
- data/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb +1 -1
- 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/aggregate_spec.rb +2 -1
- data/spec/mongo/operation/collections_info_spec.rb +4 -1
- data/spec/mongo/operation/command_spec.rb +6 -3
- data/spec/mongo/operation/create_index_spec.rb +6 -3
- data/spec/mongo/operation/create_user_spec.rb +6 -3
- data/spec/mongo/operation/delete/bulk_spec.rb +9 -6
- data/spec/mongo/operation/delete/op_msg_spec.rb +3 -3
- data/spec/mongo/operation/delete_spec.rb +11 -7
- data/spec/mongo/operation/drop_index_spec.rb +6 -2
- data/spec/mongo/operation/find/legacy_spec.rb +3 -1
- data/spec/mongo/operation/get_more_spec.rb +3 -1
- data/spec/mongo/operation/indexes_spec.rb +5 -1
- data/spec/mongo/operation/insert/bulk_spec.rb +10 -7
- 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/insert_spec.rb +15 -12
- data/spec/mongo/operation/map_reduce_spec.rb +5 -2
- data/spec/mongo/operation/read_preference_op_msg_spec.rb +1 -1
- data/spec/mongo/operation/remove_user_spec.rb +6 -3
- data/spec/mongo/operation/result_spec.rb +1 -1
- data/spec/mongo/operation/update/bulk_spec.rb +9 -6
- data/spec/mongo/operation/update/command_spec.rb +2 -2
- data/spec/mongo/operation/update/op_msg_spec.rb +3 -3
- data/spec/mongo/operation/update_spec.rb +10 -7
- data/spec/mongo/operation/update_user_spec.rb +4 -1
- data/spec/mongo/protocol/compressed_spec.rb +26 -12
- data/spec/mongo/query_cache_middleware_spec.rb +55 -0
- data/spec/mongo/query_cache_spec.rb +280 -0
- data/spec/mongo/retryable_spec.rb +3 -2
- data/spec/mongo/server/app_metadata_shared.rb +2 -2
- data/spec/mongo/server/app_metadata_spec.rb +2 -0
- data/spec/mongo/server/connection_pool/populator_spec.rb +3 -1
- data/spec/mongo/server/connection_pool_spec.rb +8 -4
- data/spec/mongo/server/connection_spec.rb +39 -25
- data/spec/mongo/server/description_spec.rb +18 -0
- data/spec/mongo/server/monitor/connection_spec.rb +17 -7
- data/spec/mongo/server/monitor_spec.rb +9 -1
- data/spec/mongo/server_selector_spec.rb +2 -2
- data/spec/mongo/server_spec.rb +15 -2
- data/spec/mongo/socket/ssl_spec.rb +44 -4
- data/spec/mongo/socket_spec.rb +2 -2
- data/spec/mongo/tls_context_hooks_spec.rb +37 -0
- 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 +1 -1
- data/spec/runners/connection_string.rb +31 -124
- data/spec/runners/crud/requirement.rb +40 -3
- data/spec/runners/crud/test_base.rb +0 -19
- data/spec/runners/crud/verifier.rb +8 -0
- data/spec/runners/server_selection.rb +1 -1
- data/spec/runners/transactions/operation.rb +13 -2
- data/spec/runners/transactions/test.rb +3 -2
- data/spec/runners/unified.rb +96 -0
- data/spec/runners/unified/assertions.rb +249 -0
- data/spec/runners/unified/change_stream_operations.rb +26 -0
- data/spec/runners/unified/crud_operations.rb +199 -0
- data/spec/runners/unified/ddl_operations.rb +96 -0
- data/spec/runners/unified/entity_map.rb +39 -0
- data/spec/runners/unified/error.rb +25 -0
- data/spec/runners/unified/event_subscriber.rb +91 -0
- data/spec/runners/unified/exceptions.rb +21 -0
- data/spec/runners/unified/grid_fs_operations.rb +55 -0
- data/spec/runners/unified/support_operations.rb +250 -0
- data/spec/runners/unified/test.rb +393 -0
- data/spec/runners/unified/test_group.rb +28 -0
- data/spec/runners/unified/using_hash.rb +31 -0
- data/spec/shared/LICENSE +20 -0
- data/spec/shared/bin/get-mongodb-download-url +17 -0
- data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
- data/spec/shared/lib/mrss/cluster_config.rb +218 -0
- data/spec/shared/lib/mrss/constraints.rb +346 -0
- data/spec/shared/lib/mrss/docker_runner.rb +262 -0
- data/spec/shared/lib/mrss/lite_constraints.rb +175 -0
- data/spec/shared/lib/mrss/server_version_registry.rb +112 -0
- data/spec/shared/lib/mrss/spec_organizer.rb +149 -0
- data/spec/shared/lib/mrss/utils.rb +15 -0
- data/spec/shared/share/Dockerfile.erb +231 -0
- data/spec/shared/shlib/distro.sh +73 -0
- data/spec/shared/shlib/server.sh +290 -0
- data/spec/shared/shlib/set_env.sh +128 -0
- data/spec/solo/clean_exit_spec.rb +21 -0
- data/spec/spec_helper.rb +7 -2
- data/spec/spec_tests/cmap_spec.rb +7 -3
- data/spec/spec_tests/crud_unified_spec.rb +10 -0
- data/spec/spec_tests/data/change_streams/change-streams-errors.yml +0 -1
- data/spec/spec_tests/data/change_streams/change-streams.yml +0 -2
- 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/crud_unified/estimatedDocumentCount.yml +267 -0
- data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-4.9.yml +60 -0
- data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount.yml → estimatedDocumentCount-pre4.9.yml} +2 -0
- data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-serverErrors-4.9.yml +146 -0
- data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount-serverErrors.yml → estimatedDocumentCount-serverErrors-pre4.9.yml} +2 -0
- data/spec/spec_tests/data/retryable_reads/listIndexNames.yml +1 -1
- 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/unified/valid-fail/operation-failure.yml +31 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml +220 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-command-monitoring.yml +102 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +184 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-gridfs.yml +155 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml +193 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +210 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +215 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +235 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +169 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +170 -0
- data/spec/spec_tests/data/uri_options/auth-options.yml +25 -0
- data/spec/spec_tests/data/uri_options/compression-options.yml +7 -4
- 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/data/versioned_api/crud-api-version-1-strict.yml +416 -0
- data/spec/spec_tests/data/versioned_api/crud-api-version-1.yml +409 -0
- data/spec/spec_tests/data/versioned_api/runcommand-helper-no-api-version-declared.yml +67 -0
- data/spec/spec_tests/data/versioned_api/test-commands-deprecation-errors.yml +47 -0
- data/spec/spec_tests/data/versioned_api/test-commands-strict-mode.yml +44 -0
- data/spec/spec_tests/data/versioned_api/transaction-handling.yml +180 -0
- data/spec/spec_tests/dns_seedlist_discovery_spec.rb +9 -1
- data/spec/spec_tests/unified_spec.rb +15 -0
- data/spec/spec_tests/uri_options_spec.rb +47 -33
- data/spec/spec_tests/versioned_api_spec.rb +10 -0
- 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_macros.rb +11 -2
- data/spec/support/common_shortcuts.rb +59 -0
- data/spec/support/constraints.rb +6 -253
- 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/session.rb +2 -2
- data/spec/support/spec_config.rb +68 -3
- data/spec/support/spec_setup.rb +48 -38
- data/spec/support/utils.rb +102 -4
- metadata +1087 -936
- metadata.gz.sig +0 -0
- data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +0 -56
- data/lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb +0 -47
- data/spec/support/child_process_helper.rb +0 -78
- data/spec/support/cluster_config.rb +0 -207
- data/spec/support/lite_constraints.rb +0 -141
- data/spec/support/spec_organizer.rb +0 -129
@@ -63,6 +63,11 @@ module Mongo
|
|
63
63
|
end
|
64
64
|
|
65
65
|
result = handshake!(speculative_auth_doc: speculative_auth_doc)
|
66
|
+
|
67
|
+
if description.unknown?
|
68
|
+
raise Error::InternalDriverError, "Connection description cannot be unknown after successful handshake: #{description.inspect}"
|
69
|
+
end
|
70
|
+
|
66
71
|
if speculative_auth_doc && (speculative_auth_result = result['speculativeAuthenticate'])
|
67
72
|
unless description.features.scram_sha_1_enabled?
|
68
73
|
raise Error::InvalidServerAuthResponse, "Speculative auth succeeded on a pre-3.0 server"
|
@@ -80,11 +85,15 @@ module Mongo
|
|
80
85
|
speculative_auth_result: speculative_auth_result,
|
81
86
|
)
|
82
87
|
else
|
83
|
-
raise
|
88
|
+
raise Error::InternalDriverError, "Speculative auth unexpectedly succeeded for mechanism #{speculative_auth_user.mechanism.inspect}"
|
84
89
|
end
|
85
90
|
elsif !description.arbiter?
|
86
91
|
authenticate!
|
87
92
|
end
|
93
|
+
|
94
|
+
if description.unknown?
|
95
|
+
raise Error::InternalDriverError, "Connection description cannot be unknown after successful authentication: #{description.inspect}"
|
96
|
+
end
|
88
97
|
end
|
89
98
|
|
90
99
|
private
|
@@ -96,7 +105,7 @@ module Mongo
|
|
96
105
|
# this particular connection.
|
97
106
|
def handshake!(speculative_auth_doc: nil)
|
98
107
|
unless socket
|
99
|
-
raise Error::
|
108
|
+
raise Error::InternalDriverError, "Cannot handshake because there is no usable socket (for #{address})"
|
100
109
|
end
|
101
110
|
|
102
111
|
ismaster_doc = app_metadata.validated_document
|
@@ -104,21 +113,30 @@ module Mongo
|
|
104
113
|
ismaster_doc = ismaster_doc.merge(speculativeAuthenticate: speculative_auth_doc)
|
105
114
|
end
|
106
115
|
|
116
|
+
if server_api = options[:server_api]
|
117
|
+
ismaster_doc = ismaster_doc.merge(
|
118
|
+
Utils.transform_server_api(server_api)
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
107
122
|
ismaster_command = Protocol::Query.new(Database::ADMIN, Database::COMMAND,
|
108
123
|
ismaster_doc, :limit => -1)
|
109
124
|
|
110
|
-
|
125
|
+
doc = nil
|
111
126
|
@server.handle_handshake_failure! do
|
112
127
|
begin
|
113
128
|
response = @server.round_trip_time_averager.measure do
|
114
129
|
add_server_diagnostics do
|
115
130
|
socket.write(ismaster_command.serialize.to_s)
|
116
|
-
Protocol::Message.deserialize(socket, Protocol::Message::MAX_MESSAGE_SIZE)
|
131
|
+
Protocol::Message.deserialize(socket, Protocol::Message::MAX_MESSAGE_SIZE)
|
117
132
|
end
|
118
133
|
end
|
134
|
+
result = Operation::Result.new([response])
|
135
|
+
result.validate!
|
136
|
+
doc = result.documents.first
|
119
137
|
rescue => exc
|
120
138
|
msg = "Failed to handshake with #{address}"
|
121
|
-
Utils.
|
139
|
+
Utils.warn_bg_exception(msg, exc,
|
122
140
|
logger: options[:logger],
|
123
141
|
log_prefix: options[:log_prefix],
|
124
142
|
bg_error_backtrace: options[:bg_error_backtrace],
|
@@ -127,9 +145,9 @@ module Mongo
|
|
127
145
|
end
|
128
146
|
end
|
129
147
|
|
130
|
-
post_handshake(
|
148
|
+
post_handshake(doc, @server.round_trip_time_averager.average_round_trip_time)
|
131
149
|
|
132
|
-
|
150
|
+
doc
|
133
151
|
end
|
134
152
|
|
135
153
|
# @param [ String | nil ] speculative_auth_client_nonce The client
|
@@ -158,7 +176,7 @@ module Mongo
|
|
158
176
|
auth.login
|
159
177
|
rescue => exc
|
160
178
|
msg = "Failed to authenticate to #{address}"
|
161
|
-
Utils.
|
179
|
+
Utils.warn_bg_exception(msg, exc,
|
162
180
|
logger: options[:logger],
|
163
181
|
log_prefix: options[:log_prefix],
|
164
182
|
bg_error_backtrace: options[:bg_error_backtrace],
|
@@ -33,6 +33,9 @@ module Mongo
|
|
33
33
|
if topology_version.nil?
|
34
34
|
raise ArgumentError, 'Topology version must be provided but it was nil'
|
35
35
|
end
|
36
|
+
unless options[:app_metadata]
|
37
|
+
raise ArgumentError, 'App metadata is required'
|
38
|
+
end
|
36
39
|
@monitor = monitor
|
37
40
|
@topology_version = topology_version
|
38
41
|
@monitoring = monitoring
|
@@ -101,7 +104,7 @@ module Mongo
|
|
101
104
|
end
|
102
105
|
rescue Mongo::Error => exc
|
103
106
|
msg = "Error running awaited ismaster on #{server.address}"
|
104
|
-
Utils.
|
107
|
+
Utils.warn_bg_exception(msg, exc,
|
105
108
|
logger: options[:logger],
|
106
109
|
log_prefix: options[:log_prefix],
|
107
110
|
bg_error_backtrace: options[:bg_error_backtrace],
|
@@ -139,7 +142,9 @@ module Mongo
|
|
139
142
|
raise
|
140
143
|
end
|
141
144
|
@server_pushing = resp_msg.flags.include?(:more_to_come)
|
142
|
-
result = resp_msg
|
145
|
+
result = Operation::Result.new(resp_msg)
|
146
|
+
result.validate!
|
147
|
+
result.documents.first
|
143
148
|
end
|
144
149
|
|
145
150
|
def write_ismaster
|
@@ -147,6 +152,11 @@ module Mongo
|
|
147
152
|
topologyVersion: topology_version.to_doc,
|
148
153
|
maxAwaitTimeMS: monitor.heartbeat_interval * 1000,
|
149
154
|
)
|
155
|
+
if server_api = options[:server_api]
|
156
|
+
payload.update(
|
157
|
+
Utils.transform_server_api(server_api)
|
158
|
+
)
|
159
|
+
end
|
150
160
|
|
151
161
|
req_msg = Protocol::Msg.new([:exhaust_allowed], {}, payload)
|
152
162
|
@lock.synchronize { @connection }.write_bytes(req_msg.serialize.to_s)
|
@@ -265,7 +265,11 @@ module Mongo
|
|
265
265
|
end
|
266
266
|
end
|
267
267
|
|
268
|
-
msg = "No #{name} server
|
268
|
+
msg = "No #{name} server"
|
269
|
+
if is_a?(ServerSelector::Secondary) && !tag_sets.empty?
|
270
|
+
msg += " with tag sets: #{tag_sets}"
|
271
|
+
end
|
272
|
+
msg += " is available in cluster: #{cluster.summary} " +
|
269
273
|
"with timeout=#{server_selection_timeout}, " +
|
270
274
|
"LT=#{local_threshold_with_cluster(cluster)}"
|
271
275
|
msg += server_selection_diagnostic_message(cluster)
|
data/lib/mongo/session.rb
CHANGED
@@ -379,6 +379,7 @@ module Mongo
|
|
379
379
|
rv = yield self
|
380
380
|
rescue Exception => e
|
381
381
|
if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
|
382
|
+
log_warn("Aborting transaction due to #{e.class}: #{e}")
|
382
383
|
abort_transaction
|
383
384
|
transaction_in_progress = false
|
384
385
|
end
|
@@ -443,7 +444,7 @@ module Mongo
|
|
443
444
|
true
|
444
445
|
ensure
|
445
446
|
if transaction_in_progress
|
446
|
-
log_warn('with_transaction callback
|
447
|
+
log_warn('with_transaction callback broke out of with_transaction loop, aborting transaction')
|
447
448
|
begin
|
448
449
|
abort_transaction
|
449
450
|
rescue Error::OperationFailure, Error::InvalidTransactionOperation
|
@@ -534,6 +535,7 @@ module Mongo
|
|
534
535
|
#
|
535
536
|
# @since 2.6.0
|
536
537
|
def commit_transaction(options=nil)
|
538
|
+
QueryCache.clear
|
537
539
|
check_if_ended!
|
538
540
|
check_if_no_transaction!
|
539
541
|
|
@@ -580,7 +582,7 @@ module Mongo
|
|
580
582
|
txn_num: txn_num,
|
581
583
|
write_concern: write_concern,
|
582
584
|
}
|
583
|
-
Operation::Command.new(spec).execute(server, client: @client)
|
585
|
+
Operation::Command.new(spec).execute(server, context: Operation::Context.new(client: @client, session: self))
|
584
586
|
end
|
585
587
|
end
|
586
588
|
ensure
|
@@ -602,6 +604,8 @@ module Mongo
|
|
602
604
|
#
|
603
605
|
# @since 2.6.0
|
604
606
|
def abort_transaction
|
607
|
+
QueryCache.clear
|
608
|
+
|
605
609
|
check_if_ended!
|
606
610
|
check_if_no_transaction!
|
607
611
|
|
@@ -625,7 +629,7 @@ module Mongo
|
|
625
629
|
db_name: 'admin',
|
626
630
|
session: self,
|
627
631
|
txn_num: txn_num
|
628
|
-
).execute(server, client: @client)
|
632
|
+
).execute(server, context: Operation::Context.new(client: @client, session: self))
|
629
633
|
end
|
630
634
|
end
|
631
635
|
|
@@ -119,8 +119,10 @@ module Mongo
|
|
119
119
|
},
|
120
120
|
db_name: Database::ADMIN,
|
121
121
|
)
|
122
|
-
|
123
|
-
|
122
|
+
context = Operation::Context.new(options: {
|
123
|
+
server_api: server.options[:server_api],
|
124
|
+
})
|
125
|
+
op.execute(server, context: context)
|
124
126
|
end
|
125
127
|
rescue Mongo::Error, Error::AuthError
|
126
128
|
end
|
data/lib/mongo/socket.rb
CHANGED
@@ -15,6 +15,8 @@
|
|
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
|
|
@@ -25,10 +27,11 @@ module Mongo
|
|
25
27
|
class Socket
|
26
28
|
include ::Socket::Constants
|
27
29
|
|
28
|
-
# Error message for
|
30
|
+
# Error message for TLS related exceptions.
|
29
31
|
#
|
30
32
|
# @since 2.0.0
|
31
|
-
|
33
|
+
# @deprecated
|
34
|
+
SSL_ERROR = 'MongoDB may not be configured with TLS support'.freeze
|
32
35
|
|
33
36
|
# Error message for timeouts on socket calls.
|
34
37
|
#
|
@@ -129,7 +132,7 @@ module Mongo
|
|
129
132
|
sock_arr = [ @socket ]
|
130
133
|
if Kernel::select(sock_arr, nil, sock_arr, 0)
|
131
134
|
# The eof? call is supposed to return immediately since select
|
132
|
-
# indicated the socket is readable. However, if @socket is
|
135
|
+
# indicated the socket is readable. However, if @socket is a TLS
|
133
136
|
# socket, eof? can block anyway - see RUBY-2140.
|
134
137
|
begin
|
135
138
|
Timeout.timeout(0.1) do
|
@@ -152,7 +155,14 @@ module Mongo
|
|
152
155
|
#
|
153
156
|
# @since 2.0.0
|
154
157
|
def close
|
155
|
-
|
158
|
+
begin
|
159
|
+
# Sometimes it seems the close call can hang for a long time
|
160
|
+
::Timeout.timeout(5) do
|
161
|
+
@socket.close
|
162
|
+
end
|
163
|
+
rescue
|
164
|
+
# Silence all errors
|
165
|
+
end
|
156
166
|
true
|
157
167
|
end
|
158
168
|
|
@@ -324,8 +334,21 @@ module Mongo
|
|
324
334
|
else
|
325
335
|
select_args = [nil, [@socket], [@socket], select_timeout]
|
326
336
|
end
|
327
|
-
|
328
|
-
|
337
|
+
rv = Kernel.select(*select_args)
|
338
|
+
if BSON::Environment.jruby?
|
339
|
+
# Ignore the return value of Kernel.select.
|
340
|
+
# On JRuby, select appears to return nil prior to timeout expiration
|
341
|
+
# (apparently due to a EAGAIN) which then causes us to fail the read
|
342
|
+
# even though we could have retried it.
|
343
|
+
# Check the deadline ourselves.
|
344
|
+
if deadline
|
345
|
+
select_timeout = deadline - Time.now
|
346
|
+
if select_timeout <= 0
|
347
|
+
raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data"
|
348
|
+
end
|
349
|
+
end
|
350
|
+
elsif rv.nil?
|
351
|
+
raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data (select call timed out)"
|
329
352
|
end
|
330
353
|
retry
|
331
354
|
end
|
@@ -342,7 +365,7 @@ module Mongo
|
|
342
365
|
end
|
343
366
|
|
344
367
|
def read_buffer_size
|
345
|
-
# Buffer size for non-
|
368
|
+
# Buffer size for non-TLS reads
|
346
369
|
# 64kb
|
347
370
|
65536
|
348
371
|
end
|
@@ -396,6 +419,10 @@ module Mongo
|
|
396
419
|
set_option(sock, :TCP_KEEPCNT, DEFAULT_TCP_KEEPCNT)
|
397
420
|
set_option(sock, :TCP_KEEPIDLE, DEFAULT_TCP_KEEPIDLE)
|
398
421
|
rescue
|
422
|
+
# JRuby 9.2.13.0 and lower do not define TCP_KEEPINTVL etc. constants.
|
423
|
+
# JRuby 9.2.14.0 defines the constants but does not allow to get or
|
424
|
+
# set them with this error:
|
425
|
+
# Errno::ENOPROTOOPT: Protocol not available - Protocol not available
|
399
426
|
end
|
400
427
|
|
401
428
|
def set_option(sock, option, default)
|
@@ -420,7 +447,7 @@ module Mongo
|
|
420
447
|
rescue IOError, SystemCallError => e
|
421
448
|
raise Error::SocketError, "#{e.class}: #{e} (for #{human_address})"
|
422
449
|
rescue OpenSSL::SSL::SSLError => e
|
423
|
-
raise Error::SocketError, "#{e.class}: #{e} (for #{human_address})
|
450
|
+
raise Error::SocketError, "#{e.class}: #{e} (for #{human_address})"
|
424
451
|
end
|
425
452
|
end
|
426
453
|
|
@@ -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
|