在从Node.js应用程序查询托管在Heroku上的Postgres数据库时出现“自签名证书”错误。

65
我的Node.js应用程序可以通过npm pg模块与本地的Postgres数据库配合使用。 我也可以通过命令行使用heroku pg:psql命令连接到托管在Heroku上的Postgres数据库(免费的Hobby Dev计划)。 但是,当我的Node.js应用程序尝试查询托管在Heroku上的Postgres数据库时,我会收到一个“自签名证书”错误。 以下是带有“自签名证书”错误的输出内容:
(node:2100) UnhandledPromiseRejectionWarning: Error: self signed certificate
    at TLSSocket.onConnectSecure (_tls_wrap.js:1051:34)
    at TLSSocket.emit (events.js:189:13)
    at TLSSocket._finishInit (_tls_wrap.js:633:8)
(node:2100) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:2100) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
D:\MY\DEV\PROJECTS\AdsSubscribeBot\test.js:57
  if (err) throw err;
           ^

Error: Connection terminated unexpectedly
    at Connection.con.once (D:\MY\DEV\PROJECTS\AdsSubscribeBot\node_modules\pg\lib\client.js:264:9)
    at Object.onceWrapper (events.js:277:13)
    at Connection.emit (events.js:189:13)
    at Socket.<anonymous> (D:\MY\DEV\PROJECTS\AdsSubscribeBot\node_modules\pg\lib\connection.js:76:10)
    at Socket.emit (events.js:194:15)
    at TCP._handle.close (net.js:597:12)

重现此错误的最简单方式是尝试使用Heroku开发中心中提供的Node.js示例代码进行连接:

https://devcenter.heroku.com/articles/heroku-postgresql#connecting-in-node-js

以下是引起self signed certificate错误的代码示例:

const connectionString = 'postgres://USERNAME:PASSWORD@HOST:PORT/DB_NAME';

const { Client } = require('pg');

const client = new Client({
  connectionString: connectionString,
  ssl: true
});

client.connect();

client.query('SELECT * FROM users;', (err, res) => {
  if (err) throw err;
  for (let row of res.rows) {
    console.log(JSON.stringify(row));
  }
  client.end();
});
也许有人曾经遇到过相同的问题并知道如何解决它。提前感谢任何帮助。

2
我找到的唯一解决方法是在您的环境中设置NODE_TLS_REJECT_UNAUTHORIZED=0。我不会将其发布为答案,因为它是一种黑客行为且不安全,但希望有人能够在某个时候发布实际解决方案。 - Rick Mogstad
1
@RickMogstad 谢谢,但是是一个hack :) 但我想知道原因。不幸的是,我无法创建一个Heroku支持团队的工单,因为我使用的是免费计划... :) 所以唯一的选择是向社区提问。 - akzhar
5个回答

112

请检查您的pg配置。看起来您正在使用已废弃对证书验证的隐式禁用(因为在您的配置中ssl设置为true,但未提供ssl配置)的pg 8。在您的pg配置中指定rejectUnauthorized: true以要求有效的CA或rejectUnauthorized: false以明确选择退出中间人攻击保护。

您可以在设置pg配置时执行此操作,如下所示:

const client = new Client({
  connectionString: connectionString,
  ssl: { rejectUnauthorized: false }
})

6
为了让代码可以在生产环境和本地机器上都能运行,我不得不做出以下更改: ssl: this.isProduction() ? { rejectUnauthorized: false } : false。其中,ssl 参数在生产环境下需要设置为 { rejectUnauthorized: false },而在本地机器上则需要设置为 false - Renato Pereira
9
对我而言,只有设置环境变量才有效:PGSSLMODE=no-verify (参见Heroku文档:https://devcenter.heroku.com/articles/heroku-postgresql#connecting-in-node-js) - Freewalker
尝试使用以下代码进行Knex pg连接:<code> const _connectionDetails = { host: process.env.PG_HOST, user: process.env.PG_USERNAME, password: process.env.PG_PASSWORD, database: process.env.PG_DB, ssl: {rejectUnauthorized: false} } const connection = knex({ client: 'pg', connection: _connectionDetails, debug: false//(process.env.APP_ENV !== 'production') }); </code> - sathya seelan
我们是否应该将“requestCert”选项也设置为“true”,它是“rejectUnauthorized”的兄弟选项,因为它的文档说仅当“requestCert”为“true”时,“rejectUnauthorized”才会生效,“requestCert”的默认值为“false”。 - sçuçu
3
我觉得很惊讶,几乎每个答案都建议使用这种不安全的方法,这会使开发者容易受到中间人攻击的威胁。 - Emilia Bopp
显示剩余2条评论

15

如果在将SSL对象添加到客户端对象并且使用连接字符串后仍然遇到问题,请确保连接字符串中没有ssl参数。如果您正在使用Digital Ocean,则此参数包含在生成的连接字符串中。

Digital Ocean默认情况下是这样格式化连接字符串的:

postgres://USERNAME:PASSWORD@HOST:PORT/DB_NAME:25060/defaultdb?&sslmode=require


3
绝对必要:在我找到这个之前,我浪费了3个小时的生命。 - Femi
2
啊,是的。我正在使用Heroku。但是同样的问题仍然存在。我在结尾处添加了"?ssl=true"。谢谢! - bentedder
1
从Sequelize 6.7.0迁移到6.12.0时遇到了同样的问题,删除sslmode参数解决了该问题。 - Damien

14
为了让此功能正常运作,我需要添加以下内容:
ssl: { rejectUnauthorized: false }

但是也需要将这个内容添加到环境中:

NODE_TLS_REJECT_UNAUTHORIZED=0

1
这是一个糟糕的决定... - hdoitc
@hdoitc 现在只有使用这个才能使它工作,为什么这是一个不好的决定? - Sergio Toledo Piza
@SergioToledoPiza 这是不安全的,因为它指定允许中间人攻击。 - SurfingSanta

7

以下是使用Knex.js的已接受答案的变体。在Heroku上进行了测试。

const parse = require('pg-connection-string').parse;
const pgconfig = parse('your-pg-connection-string');
pgconfig.ssl = { rejectUnauthorized: false };

const knex = Knex({
  client: 'pg',
  connection: pgconfig,
});

1
没有其他我尝试的方法是有效的。这是唯一有效的方法,但它又带来了另一个依赖...记得安装 npm install --save pg-connection-string 才能让它生效。 - nickang
@nickang - 其他方法是否无法工作是因为您的数据库连接字符串中包含 sslsslmode?https://dev59.com/n1IH5IYBdhLWcg3wAHj2#69994711 - Matt Sanders

3

感谢@samkhan27的帮助。我刚刚添加了ssl: { rejectUnauthorized: false }

我的完整代码:

const db = new Sequelize(
process.env.DATABASE_URL ||
`postgres://postgres:w2w2@localhost:5432/${databaseName}`, 
{
    logging: false,
    ssl: { rejectUnauthorized: false } //solved the problem with self signed sertificate
}    

)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接