/** * Copyright (C) 2020-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include #include #include #include "mongo/base/error_codes.h" #include "mongo/base/status_with.h" #include "mongo/bson/bsonelement.h" #include "mongo/bson/bsontypes.h" #include "mongo/bson/bsontypes_util.h" #include "mongo/bson/timestamp.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/crypto/hash_block.h" #include "mongo/crypto/sha1_block.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/client.h" #include "mongo/db/logical_time_validator.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/replication_coordinator.h" #include "mongo/db/server_options.h" #include "mongo/db/signed_logical_time.h" #include "mongo/db/time_proof_service.h" #include "mongo/db/vector_clock.h" #include "mongo/db/vector_clock_document_gen.h" #include "mongo/db/vector_clock_gen.h" #include "mongo/util/assert_util.h" #include "mongo/util/clock_source.h" #include "mongo/util/decorable.h" #include "mongo/util/duration.h" #include "mongo/util/str.h" #include "mongo/util/time_support.h" #include "mongo/util/version/releases.h" namespace mongo { namespace { const auto vectorClockCommonDecoration = ServiceContext::declareDecoration(); const auto vectorClockDecoration = ServiceContext::declareDecoration(); ServiceContext::ConstructorActionRegisterer vectorClockRegisterer( "VectorClock", [](ServiceContext* service) { VectorClock::registerVectorClockOnServiceContext(service, &vectorClockCommonDecoration(service)); }); } // namespace const LogicalTime VectorClock::kInitialComponentTime{Timestamp{0, 1}}; VectorClock* VectorClock::get(ServiceContext* service) { return vectorClockDecoration(service); } VectorClock* VectorClock::get(OperationContext* ctx) { return get(ctx->getClient()->getServiceContext()); } const VectorClock* VectorClock::get(const ServiceContext* service) { return vectorClockDecoration(service); } const VectorClock* VectorClock::get(const OperationContext* ctx) { return get(ctx->getClient()->getServiceContext()); } void VectorClock::registerVectorClockOnServiceContext(ServiceContext* service, VectorClock* vectorClock) { vectorClock->_service = service; auto& clock = vectorClockDecoration(service); clock = std::move(vectorClock); } VectorClock::VectorTime VectorClock::getTime() const { stdx::lock_guard lock(_mutex); return VectorTime(_vectorTime); } bool VectorClock::_lessThanOrEqualToMaxPossibleTime(LogicalTime time, uint64_t nTicks) { return time.asTimestamp().getSecs() <= kMaxValue && time.asTimestamp().getInc() <= (kMaxValue - nTicks); } void VectorClock::_ensurePassesRateLimiter(ServiceContext* service, const LogicalTimeArray& newTime) { const unsigned wallClockSecs = durationCount(service->getFastClockSource()->now().toDurationSinceEpoch()); auto maxAcceptableDriftSecs = static_cast(gMaxAcceptableLogicalClockDriftSecs); for (auto newIt = newTime.begin(); newIt != newTime.end(); ++newIt) { auto newTimeSecs = newIt->asTimestamp().getSecs(); auto name = _componentName(Component(newIt - newTime.begin())); // Both values are unsigned, so compare them first to avoid wrap-around. uassert(ErrorCodes::ClusterTimeFailsRateLimiter, str::stream() << "New " << name << ", " << newTimeSecs << ", is too far from this node's wall clock time, " << wallClockSecs << ".", ((newTimeSecs <= wallClockSecs) || (newTimeSecs - wallClockSecs) <= maxAcceptableDriftSecs)); uassert(40484, str::stream() << name << " cannot be advanced beyond its maximum value", _lessThanOrEqualToMaxPossibleTime(*newIt, 0)); } } void VectorClock::_advanceTime(LogicalTimeArray&& newTime) { _ensurePassesRateLimiter(_service, newTime); stdx::lock_guard lock(_mutex); auto it = _vectorTime.begin(); auto newIt = newTime.begin(); for (; it != _vectorTime.end() && newIt != newTime.end(); ++it, ++newIt) { if (*newIt > *it) { *it = std::move(*newIt); } } } class VectorClock::PlainComponentFormat : public VectorClock::ComponentFormat { public: using ComponentFormat::ComponentFormat; ~PlainComponentFormat() override = default; bool out(ServiceContext* service, OperationContext* opCtx, BSONObjBuilder* out, LogicalTime time, Component component, bool isInternal) const override { out->append(_fieldName, time.asTimestamp()); return true; } }; LogicalTime fromOptionalTimestamp(const boost::optional& time) { return time ? LogicalTime(*time) : LogicalTime(); } class VectorClock::ConfigTimeComponent : public VectorClock::PlainComponentFormat { public: ConfigTimeComponent() : PlainComponentFormat(VectorClock::kConfigTimeFieldName) {} ~ConfigTimeComponent() override = default; LogicalTime in(ServiceContext* service, OperationContext* opCtx, const GossipedVectorClockComponents& timepoints, bool couldBeUnauthenticated, Component component) const override { return fromOptionalTimestamp(timepoints.getDollarConfigTime()); } }; class VectorClock::TopologyTimeComponent : public VectorClock::PlainComponentFormat { public: TopologyTimeComponent() : PlainComponentFormat(VectorClock::kTopologyTimeFieldName) {} ~TopologyTimeComponent() override = default; LogicalTime in(ServiceContext* service, OperationContext* opCtx, const GossipedVectorClockComponents& timepoints, bool couldBeUnauthenticated, Component component) const override { return fromOptionalTimestamp(timepoints.getDollarTopologyTime()); } }; class VectorClock::SignedComponentFormat : public VectorClock::ComponentFormat { public: using ComponentFormat::ComponentFormat; ~SignedComponentFormat() override = default; bool out(ServiceContext* service, OperationContext* opCtx, BSONObjBuilder* out, LogicalTime time, Component component, bool isInternal) const override { SignedLogicalTime signedTime; if (isInternal || (opCtx && LogicalTimeValidator::isAuthorizedToAdvanceClock(opCtx))) { // Authorized clients always receive a dummy-signed $clusterTime (and operationTime). // Internal clients can always be considered authorized. signedTime = SignedLogicalTime(time, TimeProofService::TimeProof(), 0); } else { // Servers without validators (e.g. a shard server not yet added to a cluster) do not // return logical times to unauthorized clients. auto validator = LogicalTimeValidator::get(service); if (!validator) { return false; } // TODO (SERVER-87463): Investigate why the embedded router is not able to sign once the // router port is opened. // There are some contexts where refreshing is not permitted. if (opCtx && serverGlobalParams.clusterRole.hasExclusively(ClusterRole::RouterServer)) { signedTime = validator->signLogicalTime(opCtx, time); } else { signedTime = validator->trySignLogicalTime(time); } // If there were no keys, do not return $clusterTime (or operationTime) to unauthorized // clients. if (signedTime.getKeyId() == 0) { return false; } } BSONObjBuilder subObjBuilder(out->subobjStart(_fieldName)); signedTime.getTime().asTimestamp().append(subObjBuilder.bb(), kClusterTimeFieldName); // TODO SERVER-88458: Remove FCV check when all servers can be assumed to handle a missing // signature. // // (Generic FCV reference): This is not a truly generic FCV reference and will be removed in // the next release branch. It guards a message format change for the vector clock that // isn't worth its own feature flag. This comment avoids a linter error. auto fcvSnapshot = serverGlobalParams.featureCompatibility.acquireFCVSnapshot(); // As an optimization, send no signature instead of a dummy signature. For legacy // compatibility, external authorized clients always receive a signature. auto shouldSkipSignature = isInternal && signedTime.getKeyId() == 0 && fcvSnapshot.isVersionInitialized() && fcvSnapshot.getVersion() == multiversion::GenericFCV::kLatest; if (!shouldSkipSignature) { BSONObjBuilder signatureObjBuilder(subObjBuilder.subobjStart(kSignatureFieldName)); // Cluster time metadata is only written when the LogicalTimeValidator is set, which // means the cluster time should always have a proof. invariant(signedTime.getProof()); signedTime.getProof()->appendAsBinData(signatureObjBuilder, kSignatureHashFieldName); signatureObjBuilder.append(kSignatureKeyIdFieldName, signedTime.getKeyId()); signatureObjBuilder.doneFast(); } subObjBuilder.doneFast(); return true; } LogicalTime in(ServiceContext* service, OperationContext* opCtx, const GossipedVectorClockComponents& timepoints, bool couldBeUnauthenticated, Component component) const override { if (!timepoints.getDollarClusterTime()) { // Nothing to gossip in. return LogicalTime(); } auto& clusterTime = *(timepoints.getDollarClusterTime()); Timestamp ts = clusterTime.getClusterTime(); // Parse the signature, treating no signature the same as a dummy signed proof. auto proof = TimeProofService::TimeProof(); long long keyId = 0; if (clusterTime.getSignature()) { auto hashCDR = clusterTime.getSignature()->getHash(); auto hashLength = hashCDR.length(); auto rawBinSignature = reinterpret_cast(hashCDR.data()); BSONBinData proofBinData(rawBinSignature, hashLength, BinDataType::BinDataGeneral); proof = uassertStatusOK(SHA1Block::fromBinData(proofBinData)); keyId = clusterTime.getSignature()->getKeyId(); } auto signedTime = SignedLogicalTime(LogicalTime(ts), std::move(proof), keyId); if (!opCtx) { // If there's no opCtx then this must be coming from a reply, which must be internal, // and so doesn't require validation. return signedTime.getTime(); } // Validate the signature. if (couldBeUnauthenticated && AuthorizationManager::get(opCtx->getService())->isAuthEnabled() && (!signedTime.getProof() || *signedTime.getProof() == TimeProofService::TimeProof())) { AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient()); // The client is not authenticated and is not using localhost auth bypass. Do not // gossip. if (authSession && !authSession->isAuthenticated() && !authSession->isUsingLocalhostBypass()) { return {}; } } if (!LogicalTimeValidator::isAuthorizedToAdvanceClock(opCtx)) { auto logicalTimeValidator = LogicalTimeValidator::get(service); if (!logicalTimeValidator) { uasserted(ErrorCodes::CannotVerifyAndSignLogicalTime, "Cannot accept logicalTime: " + signedTime.getTime().toString() + ". May not be a part of a sharded cluster"); } else { uassertStatusOK(logicalTimeValidator->validate(opCtx, signedTime)); } } return signedTime.getTime(); } private: static constexpr char kClusterTimeFieldName[] = "clusterTime"; static constexpr char kSignatureFieldName[] = "signature"; static constexpr char kSignatureHashFieldName[] = "hash"; static constexpr char kSignatureKeyIdFieldName[] = "keyId"; }; const VectorClock::ComponentArray> VectorClock::_gossipFormatters{ std::make_unique(VectorClock::kClusterTimeFieldName), std::make_unique(), std::make_unique()}; bool VectorClock::gossipOut(OperationContext* opCtx, BSONObjBuilder* outMessage, bool forceInternal) const { if (!isEnabled()) { return false; } const auto claimsInternal = [&]() -> bool { if (forceInternal) { return true; } if (opCtx) { if (auto client = opCtx->getClient(); client && client->session()) { return opCtx->getClient()->isPossiblyUnauthenticatedInternalClient(); } } return false; }(); ComponentSet toGossip = claimsInternal ? _getGossipInternalComponents() : _getGossipExternalComponents(); auto now = getTime(); bool clusterTimeWasOutput = false; for (auto component : toGossip) { clusterTimeWasOutput |= _gossipOutComponent(opCtx, outMessage, now._time, component, claimsInternal); } return clusterTimeWasOutput; } void VectorClock::gossipIn(OperationContext* opCtx, const GossipedVectorClockComponents& timepoints, bool couldBeUnauthenticated, bool defaultIsInternalClient) { if (!isEnabled()) { return; } auto isInternal = defaultIsInternalClient; if (opCtx) { if (const auto client = opCtx->getClient()) { if (client->session() && !(client->getTags() & Client::kPending)) { isInternal = client->isInternalClient(); } } } ComponentSet toGossip = isInternal ? _getGossipInternalComponents() : _getGossipExternalComponents(); LogicalTimeArray newTime; for (auto component : toGossip) { _gossipInComponent(opCtx, timepoints, couldBeUnauthenticated, &newTime, component); } // Since the times in LogicalTimeArray are default constructed (ie. to Timestamp(0, 0)), any // component not present in the input BSONObj won't be advanced. _advanceTime(std::move(newTime)); } bool VectorClock::_gossipOutComponent(OperationContext* opCtx, BSONObjBuilder* out, const LogicalTimeArray& time, Component component, bool isInternal) const { bool wasOutput = _gossipFormatters[component]->out( _service, opCtx, out, time[component], component, isInternal); return (component == Component::ClusterTime) ? wasOutput : false; } void VectorClock::_gossipInComponent(OperationContext* opCtx, const GossipedVectorClockComponents& timepoints, bool couldBeUnauthenticated, LogicalTimeArray* newTime, Component component) { (*newTime)[component] = _gossipFormatters[component]->in( _service, opCtx, timepoints, couldBeUnauthenticated, component); } std::string VectorClock::_componentName(Component component) { return _gossipFormatters[component]->_fieldName; } bool VectorClock::isEnabled() const { return _isEnabled.load(); } void VectorClock::_disable() { _isEnabled.store(false); } void VectorClock::resetVectorClock_forTest() { stdx::lock_guard lock(_mutex); auto it = _vectorTime.begin(); for (; it != _vectorTime.end(); ++it) { *it = VectorClock::kInitialComponentTime; } _isEnabled.store(true); } void VectorClock::_advanceTime_forTest(Component component, LogicalTime newTime) { LogicalTimeArray newTimeArray; newTimeArray[component] = newTime; _advanceTime(std::move(newTimeArray)); } bool VectorClock::_permitGossipClusterTimeWithExternalClients() const { // Permit gossiping with external clients in case this node is a standalone mongos. if (serverGlobalParams.clusterRole.hasExclusively(ClusterRole::RouterServer)) { return true; } // If this node has no replication coordinator, permit gossiping with external clients. On // the other hand, if this node has replication coordinator but it is in an unreadable state, // skip gossiping because it may require reading a signing key from the keys collection. auto replicationCoordinator = repl::ReplicationCoordinator::get(_service); return !replicationCoordinator || (replicationCoordinator->getSettings().isReplSet() && // Check repl status without locks to prevent deadlocks. This is a best effort check // as the repl state can change right after this check even when inspected under a // lock or mutex. replicationCoordinator->isInPrimaryOrSecondaryState_UNSAFE()); } } // namespace mongo