SoftInstigate / restheart

Rapid API Development with MongoDB
https://restheart.org
GNU Affero General Public License v3.0
807 stars 171 forks source link

Blacklisting MongoDB Databases / Hiding config database #347

Closed christiangroth closed 5 years ago

christiangroth commented 5 years ago

Hi,

we're currently in a situation using RH 3.9.0 and MongoDB 3.4.x and we're upgrading MongoDB to 3.6.x. During the process we encountered some issues related to the MongoDB 'config' database. In version 3.4.x this database was only used for sharded clusters, which we don't have. Starting with version 3.6.x it's also used for some other features and when bootstrapping a clean new MongoDB in version 3.6.x the config database will be created.

This leads us to the following issues we ran into:

  1. the MongoDB user we're creating during bootstrapping did not have the read permission on this database, because the role readWriteAnyDatabase does not affect local and config databases. As long as the permission is missing, RH will throw an Exception when requesting root / listing databases. Fine, we can fix that and provide the missing permission / role.
    2019-06-13 10:59:19.350 [XNIO-1 task-1 / ] ERROR org.restheart.handlers.ErrorHandler - Error handling the request
    java.lang.RuntimeException: com.mongodb.MongoQueryException: Query failed with error code 13 and error message 'not authorized on config to execute command { find: "_properties", filter: { _id: "_properties" }, limit: 1, singleBatch: true, $db: "config", $readPreference: { mode: "primaryPreferred" } }' on server caas-mongo:27017
    at org.restheart.handlers.injectors.LocalCachesSingleton.getDBProperties(LocalCachesSingleton.java:124)
    at org.restheart.handlers.root.GetRootHandler.lambda$handleRequest$1(GetRootHandler.java:87)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
    at org.restheart.handlers.root.GetRootHandler.handleRequest(GetRootHandler.java:93)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.handlers.metadata.TransformerHandler.handleRequest(TransformerHandler.java:78)
    at org.restheart.handlers.RequestDispatcherHandler.handleRequest(RequestDispatcherHandler.java:562)
    at org.restheart.handlers.injectors.CollectionPropsInjectorHandler.handleRequest(CollectionPropsInjectorHandler.java:107)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.handlers.injectors.DbPropsInjectorHandler.handleRequest(DbPropsInjectorHandler.java:92)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.handlers.injectors.AccountInjectorHandler.handleRequest(AccountInjectorHandler.java:56)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.security.handlers.AccessManagerHandler.handleRequest(AccessManagerHandler.java:61)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.security.handlers.AuthTokenInjecterHandler.handleRequest(AuthTokenInjecterHandler.java:78)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.security.handlers.AuthenticationCallHandler.handleRequest(AuthenticationCallHandler.java:54)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.security.handlers.AuthenticationConstraintHandler.handleRequest(AuthenticationConstraintHandler.java:55)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:61)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:96)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.security.handlers.SecurityHandler.handleRequest(SecurityHandler.java:76)
    at org.restheart.security.handlers.SecurityHandlerDispacher.handleRequest(SecurityHandlerDispacher.java:62)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.handlers.injectors.BodyInjectorHandler.handleRequest(BodyInjectorHandler.java:277)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.handlers.OptionsHandler.handleRequest(OptionsHandler.java:58)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.security.handlers.CORSHandler.handleRequest(CORSHandler.java:88)
    at org.restheart.handlers.RequestLoggerHandler.handleRequest(RequestLoggerHandler.java:85)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.handlers.TracingInstrumentationHandler.handleRequest(TracingInstrumentationHandler.java:39)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.handlers.MetricsInstrumentationHandler.handleRequest(MetricsInstrumentationHandler.java:59)
    at org.restheart.handlers.PipedHttpHandler.next(PipedHttpHandler.java:131)
    at org.restheart.handlers.injectors.RequestContextInjectorHandler.handleRequest(RequestContextInjectorHandler.java:691)
    at org.restheart.handlers.injectors.RequestContextInjectorHandler.handleRequest(RequestContextInjectorHandler.java:696)
    at io.undertow.server.handlers.PathHandler.handleRequest(PathHandler.java:91)
    at io.undertow.server.handlers.HttpContinueAcceptingHandler.handleRequest(HttpContinueAcceptingHandler.java:83)
    at org.restheart.handlers.ErrorHandler.handleRequest(ErrorHandler.java:70)
    at io.undertow.server.handlers.encoding.EncodingHandler.handleRequest(EncodingHandler.java:72)
    at org.restheart.handlers.GzipEncodingHandler.handleRequest(GzipEncodingHandler.java:75)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:364)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1982)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
    at java.lang.Thread.run(Thread.java:748)
    Caused by: com.mongodb.MongoQueryException: Query failed with error code 13 and error message 'not authorized on config to execute command { find: "_properties", filter: { _id: "_properties" }, limit: 1, singleBatch: true, $db: "config", $readPreference: { mode: "primaryPreferred" } }' on server caas-mongo:27017
    at com.mongodb.operation.FindOperation$1.call(FindOperation.java:707)
    at com.mongodb.operation.FindOperation$1.call(FindOperation.java:696)
    at com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.java:462)
    at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:406)
    at com.mongodb.operation.FindOperation.execute(FindOperation.java:696)
    at com.mongodb.operation.FindOperation.execute(FindOperation.java:83)
    at com.mongodb.client.internal.MongoClientDelegate$DelegateOperationExecutor.execute(MongoClientDelegate.java:179)
    at com.mongodb.client.internal.FindIterableImpl.first(FindIterableImpl.java:199)
    at org.restheart.db.DbsDAO.getDatabaseProperties(DbsDAO.java:153)
    at org.restheart.handlers.injectors.LocalCachesSingleton.lambda$setup$0(LocalCachesSingleton.java:87)
    at org.restheart.cache.impl.GuavaLoadingCache$1.load(GuavaLoadingCache.java:53)
    at org.restheart.cache.impl.GuavaLoadingCache$1.load(GuavaLoadingCache.java:50)
    at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3529)
    at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2278)
    at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2156)
    at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2046)
    at com.google.common.cache.LocalCache.get(LocalCache.java:3948)
    at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3972)
    at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4957)
    at com.google.common.cache.LocalCache$LocalLoadingCache.getUnchecked(LocalCache.java:4963)
    at org.restheart.cache.impl.GuavaLoadingCache.getLoading(GuavaLoadingCache.java:65)
    at org.restheart.handlers.injectors.LocalCachesSingleton.getDBProperties(LocalCachesSingleton.java:121)
    ... 59 common frames omitted
  2. the 'config' database is not created when starting MongoDB 3.6 (standalone) with a datadir from MongoDB 3.4 ... but this is not a RH issue, it's more like a MongoDB speciality.

Nevertheless the config database is still returned / accessible when requesting root / listing databases:

groth@mendel:~/dev/src/caas$ curl -s --header "Authorization: apikey=\"22938e42-93dd-493e-9dd4-eef8206262b8\"" http://localhost:8080/ | jq . 
{
  "_size": 2,
  "_total_pages": 1,
  "_returned": 2,
  "_embedded": {
    "rh:db": [
      {
        "_id": "my_project"
      },
      {
        "_id": "config"
      }
    ]
  }
}
MongoDB shell version v3.6.13
connecting to: mongodb://127.0.0.1:27017/?authSource=admin&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("213de41a-439d-48ee-887a-7ee238bc4508") }
MongoDB server version: 3.6.13
> show dbs
admin       0.000GB
my_project  0.000GB
config      0.000GB
local       0.000GB

At this point I'm not sure what would be the best solution. From our point of view the 'config' database is an internal database relevant for MongoDB only and should not be exposed, as it is done with the local database. This might be kind of a "bug" in RH. Otherwise an option blacklisting databases might be helpful to attack this in a more generic way.

Hint: We are not able to use mongo-mounts because the number and names of the MongoDB databases in our environment is dynamic, so we can't whitelist databases using mongo-mounts.

ujibang commented 5 years ago

In upcoming rh4 the config database is hidden (since it is more an MongoDB internal resource)

I'm going to fix this in rh3 as well

ujibang commented 5 years ago

fixed in 3148ba612b313ddb303f0475a24be84e76d10143

@mkjsix please release 3.10.1

ujibang commented 5 years ago

closing this one. @christiangroth feel free to reopen if you find any issue.

mkjsix commented 5 years ago

RESTHeart 3.10.1 has been released.

https://github.com/SoftInstigate/restheart/releases/tag/3.10.1