Meteor.js使用X509证书身份验证连接到Mongo

时间:2015-12-20 17:51:31

标签: javascript mongodb ssl meteor x509

我正在尝试在Meteor.js应用程序和Mongo服务器之间设置无密码身份验证。

为此,我需要将pem和crt文件呈现给连接。 MONGO_URL连接字符串仅关于如何执行身份验证的parameters,但不引用具有证书的文件。我假设,我需要将cert文件作为参数传递给连接。与here中描述的类似。

如何在Meteor.js中完成?

基本上我想实现相同的目标:

mongo mongo.example.com/example -ssl -sslPEMKeyFile client.pem --sslCAFile server.crt

然后如所述here

db.getSiblingDB("$external").auth(
  {
    mechanism: "MONGODB-X509",
    user: "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry"
  }
)

这在使用mogo客户端时工作正常,但在Meteor中我到目前为止只是理解我很可能需要使用下面的连接字符串(或类似的东西)

MONGO_URL=mongodb://mongo.example.com:27017/example?ssl=true&authSource=$external&authMechanism=MONGODB-X509

但问题仍然存在 - 如何将证书传递给连接?

更新:有answer使用本机noddejs mongo驱动程序处理该问题。问题是 - 如何将此移植到Meteor。

更新2015-12-31:我已经接受了在定义Collection时指向使用不同连接对象的答案。分别为每个系列做这件事很麻烦,但它似乎是现在唯一可行的方法。此外,如果需要,可能会创建一些MySslCollection,其他人可以使用它来继承连接详细信息。这尚未经过测试。

3 个答案:

答案 0 :(得分:5)

更新的答案(版本1.4及以上)

正如另一个答案所指出的,此处的选项在版本1.4中已更改,您现在可以调用

Mongo.setConnectionOptions({ ... });

请参阅the documentation了解详情。

旧答案(1.4版之前)

这是一个有趣的兔子洞...我想我找到了一个解决方案/解决方法来解决你的问题(可能还有其他人)。

我的解决方法的快速描述是在创建集合时指定mongodb服务器选项。这必须在服务器代码上完成,以避免分发您的ssl密钥/证书。像这样:

new Mongo.Collection("collection_name", {
    connection: DDP.connect("mongodb://mongo.example.com:27017/example?ssl=true&authSource=$external&authMechanism=MONGODB-X509", {
        server: {
            sslCert:[CERT_CONTENTS],
            sslKey:[KEY_CONTENTS],
            sslValidate:false
        }
    })
});

Hacky方法

既然你问过是否有办法在没有为每个创建的集合添加选项的情况下执行此操作,我决定再看看。我没有测试过这些方法,但它们看起来很合理,如果非常黑客的话。

1)在重新连接方法中使用未记录的选项参数passthru设置一个在连接状态下运行的函数。 https://github.com/meteor/meteor/blob/master/packages/ddp-client/livedata_connection.js#L996

Tracker.autorun(function () {
  var status = Meteor.status();
  if(status == 'failed')
    Meteor.reconnect({
      server: {
        sslCert:[CERT_CONTENTS],
        sslKey:[KEY_CONTENTS],
        sslValidate:false
      }
    });
});

2) Monkey修补默认的连接代码(我不太确定工作,因为我没有花时间去理解这个mongo内部代码实际上是什么时候设置的)。 https://github.com/meteor/meteor/blob/dc3cd6eb92f2bdd1bb44000cdd6abd1e5d0285b1/packages/mongo/remote_collection_driver.js

MongoInternals.defaultRemoteCollectionDriver = _.once(function () {
  var connectionOptions = {
    server: {
      sslCert:[CERT_CONTENTS],
      sslKey:[KEY_CONTENTS],
      sslValidate:false
    }
  };

  var mongoUrl = process.env.MONGO_URL;

  if (process.env.MONGO_OPLOG_URL) {
    connectionOptions.oplogUrl = process.env.MONGO_OPLOG_URL;
  }

  if (! mongoUrl)
    throw new Error("MONGO_URL must be set in environment");

  return new MongoInternals.RemoteCollectionDriver(mongoUrl, connectionOptions);
});

这些答案之旅:

从您链接的答案开始,我找到了Meteor创建其MongoDB连接的位置:https://github.com/meteor/meteor/blob/dc3cd6eb92f2bdd1bb44000cdd6abd1e5d0285b1/packages/mongo/mongo_driver.js#L173

然后我找到了被调用的地方:https://github.com/meteor/meteor/blob/3d2282d9ad0b570b913a70d215cd968019d912df/packages/mongo/remote_collection_driver.js#L4

追踪调用的地方让我看到集合实例化连接的事实:https://github.com/meteor/meteor/blob/15cdbca24888bfdff3ad43c1891a1719c09b3dc5/packages/mongo/collection.js#L102

我可以从那里看到一些选项可以覆盖使用没有指定选项的默认Mongo连接。查看集合http://docs.meteor.com/#/full/mongo_collection的Meteor文档显示:

  

选项

     

连接对象

     

将管理此集合的服务器连接。如果未指定,则使用默认连接。传递调用DDP.connect的返回值以指定其他服务器。传递null以指定无连接。非托管(名称为空)集合无法指定连接。

所以看看DDP.connect http://docs.meteor.com/#/full/ddp_connect的文档我们达到了死胡同,因为它只需要一个网址...但是等待...在搜索github上的代码之后我可以看到DDP.connect确实采用了选项参数:https://github.com/meteor/meteor/blob/master/packages/ddp-client/livedata_connection.js#L1641

这样就完成了我们的任务。

答案 1 :(得分:2)

自Meteor 1.4以来,可以通过调用:

来配置Mongo连接选项
Mongo.setConnectionOptions({ ... });

必须在初始化期间提前调用该函数。一种方法是创建一个在其他包之前加载的本地Meteor包。有关详细信息,请参阅https://forums.meteor.com/t/meteor-with-the-new-mongodb-3-2-1-compose-io/17763/10和Meteor GitHub问题#7455。

Meteor 1.4发行说明:https://github.com/meteor/meteor/blob/devel/History.md#v14-2016-07-25

答案 2 :(得分:2)

之前的两个答案都是正确的(craigtsuser1887896),但我想补充一下:

  1. 我在Atmosphere(danwild:set-connection-options)上添加了一个非常简单的包,以简化此过程(或者至少提供如何的具体示例实际上可能会使用 public static void showMenu() // displays a menu and keeps displaying until user chooses the QUIT option { Scanner myScan = new Scanner(System.in); int userChoice = 4; do { System.out.println("Choose one of the list below"); System.out.println("1. Login"); System.out.println("2. Change Password"); System.out.println("3. Change Attempts"); System.out.println("4. Quit"); if(myScan.hasNextInt()) { userChoice = myScan.nextInt(); switch (userChoice) { case 1: { System.out.println("You choose to login."); System.out.println("Please enter a password."); userPassword = myScan.next(); System.out.println("pass " + userPassword); loginAttempt(); break; } case 2: { System.out.println("You choose to change password."); System.out.println("Please enter a new password."); correctPassword = myScan.next(); break; } case 3: { System.out.println("You choose to change attempts."); System.out.println("Please enter amount of attempts."); attemptsCounter = myScan.nextInt(); break; } case 4: { System.out.println("You have quit the program."); break; } default: { System.out.println("Not a valid choice."); } }// closes switch } } while (myScan.hasNext() && userChoice != 4); myScan.close(); System.out.println("Goodbye."); }// closes showMenu1 ,因为几乎没有可用的文档。
  2. 重申一下,如果你使用任何在你的项目中访问Mongo的软件包(例如import java.util.Scanner; public class LoginMenu { private static String correctPassword = "tommybee"; public static String userPassword; public static int attemptsCounter; public static boolean loggedIn; public static void main(String[] args) { showMenu(); //loginAttempt(); } public static void showMenu() // displays a menu and keeps displaying until user chooses the QUIT option { Scanner myScan = new Scanner(System.in); int userChoice = 4; do { System.out.println("Choose one of the list below"); System.out.println("1. Login"); System.out.println("2. Change Password"); System.out.println("3. Change Attempts"); System.out.println("4. Quit"); if(myScan.hasNextInt()) { userChoice = myScan.nextInt(); switch (userChoice) { case 1: { System.out.println("You choose to login."); System.out.println("Please enter a password."); //Scanner myNewScan = new Scanner(System.in); userPassword = myScan.next(); //myNewScan.close(); System.out.println("pass " + userPassword); loginAttempt(); break; } case 2: { System.out.println("You choose to change password."); //Scanner myNewScan = new Scanner(System.in); System.out.println("Please enter a new password."); correctPassword = myScan.next(); //myNewScan.close(); break; } case 3: { System.out.println("You choose to change attempts."); //Scanner myNewScan = new Scanner(System.in); System.out.println("Please enter amount of attempts."); attemptsCounter = myScan.nextInt(); //myNewScan.close(); break; } case 4: { System.out.println("You have quit the program."); break; } default: { System.out.println("Not a valid choice."); } }// closes switch } } while (myScan.hasNext() && userChoice != 4); myScan.close(); System.out.println("Goodbye."); }// closes showMenu1 public static void loginAttempt() { //while ((correctPassword != userPassword) && (attemptsCounter >= 5)) { if (!(userPassword.toLowerCase().equals(correctPassword)) && (attemptsCounter >= 5)) { System.out.println("Please change password and attempts first."); //showMenu(); } if ((correctPassword.toLowerCase().equals(userPassword)) && (attemptsCounter <= 5)) { System.out.println("You entered the correct password in " + attemptsCounter + " attempts"); } if (attemptsCounter >= 6) { System.out.println("You have been locked out."); } } } ),你必须确保在加载之前调用Mongo.setConnectionOptions
    • 怎么做?好问题。
    • 据我所知,没有正式方法可以确保您的包裹在另一个之前加载(反过来当然也得到了很好的支持)。
    • 我见过的最简单的方法是编辑accounts-base中的包裹顺序,即将Mongo.setConnectionOptions放在文件的顶部。