brianc / node-postgres

PostgreSQL client for node.js.
https://node-postgres.com
MIT License
12.23k stars 1.22k forks source link

IP hosts are not validated correctly against certificate altnames #2263

Open RazerM opened 4 years ago

RazerM commented 4 years ago

For example, connecting to IP 1.2.3.4 yields the following error:

Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames: Host: localhost. is not in the cert's altnames: IP Address:1.2.3.4
       at Object.checkServerIdentity (tls.js:250:17)
       at TLSSocket.onConnectSecure (_tls_wrap.js:1098:27)
       at TLSSocket.emit (events.js:198:13)
       at TLSSocket._finishInit (_tls_wrap.js:666:8)

Since TLS support was added to pg, it has passed a socket to tls.connect, meaning the host should be passed separately (it isn't). It passed servername, which is not valid for IP addresses and was removed in #1890.

The reason that the error message above uses localhost can be found in _tls_wrap.js.

I found a previous issue (#2178) about this but it wasn't fixed. The correct fix is to always pass host. The documentation for options.socket says:

If this option is specified, path, host and port are ignored, except for certificate validation.

I can submit a PR but I will need help if you'd like a test for this.

hjr3 commented 4 years ago

Related to https://github.com/brianc/node-postgres-docs/issues/79

qooban commented 3 years ago

We encountered the same issue when using sequelize to connect to PG database. It's blocking us from moving forward with changing DB connection architecture.

@RazerM @charmander Do you know when it will be closed and if it will be released soon? Do you need any help to finalize it?

RazerM commented 3 years ago

The TLS issue can be demonstrated with https://1.1.1.1:

const net = require('net');
const tls = require('tls');

function connect(port, host) {
  const stream = new net.Socket();
  stream.connect(port, host);

  const options = {
    socket: stream,
    // host,
  };

  tls.connect(options);
}

connect(443, '1.1.1.1');
Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames: Host: localhost. is not in the cert's altnames: DNS:cloudflare-dns.com, DNS:*.cloudflare-dns.com, DNS:one.one.one.one, IP Address:1.1.1.1, IP Address:1.0.0.1, IP Address:162.159.36.1, IP Address:162.159.46.1, IP Address:2606:4700:4700:0:0:0:0:1111, IP Address:2606:4700:4700:0:0:0:0:1001, IP Address:2606:4700:4700:0:0:0:0:64, IP Address:2606:4700:4700:0:0:0:0:6400

Uncommenting the host variable fixes it.

paul-greco2 commented 2 months ago

Was pulling my hair out about this issue. Am putting a solution here since search engines seem to find it.

Not sure if this is the 100% best fix (but much better than rejectUnauthorized=false).

My Error: 'Hostname/IP does not match certificate's altnames: Host: localhost. is not in the cert's altnames: DNS:[address]a.us-central1.sql.goog'

Fix:

    7          ssl: {
    8           rejectUnauthorized: process.env.RUNTIME_ENV === "production", <- Keep security in prod!
    9           ca: process.env.POSTGRES_RO_SERVER_CA,   <- ca cert string
   10           key: process.env.POSTGRES_RO_CLIENT_KEY,  <- client key string
   11           cert: process.env.POSTGRES_RO_CLIENT_CERT, <- client cert string
   12           servername: process.env.POSTGRES_RO_SERVER_NAME,    <- DNS string value for db.
   13         },

Adding the expected DNS value in the error (I thought it would be in the cert too but I can't see it with openssl) as the servername makes the certs not try to verify against localhost.