AWS Lambda - Error: unable to get local issuer certificate

Viewed 5263

I am trying to connect to an amazon postgreSQL RDS using a NodeJS lambda.

The lambda is in the same VPC as the RDS instance and as far as I can tell the security groups are set up to give the lambda access to the RDS. The lambda is called through API gateway and I'm using knex js as a query builder. When the lambda attempts to connect to the database it throws an "unable to get local issuer certificate" error, but the connection parameters are what I expect them to be.

I know this connection is possible as I've already implemented it in a different environment, without receiving the certificate issue. I've compared the two environments but cannot find any immediate differences.

The connection code looks like this:

import AWS from 'aws-sdk';
import { types } from 'pg';
import { Moment } from 'moment';
import knex from 'knex';

const TIMESTAMP_OID = 1114;
// Example value string: "2018-10-04 12:30:21.199"
types.setTypeParser(TIMESTAMP_OID, (value) => value && new Date(`${value}+00`));

export default class Database {
  /**
   * Gets the connection information through AWS Secrets Manager
   */
  static getConnection = async () => {
    const client = new AWS.SecretsManager({
      region: '<region>',
    });

    if (process.env.databaseSecret == null) {
      throw 'Database secret not defined';
    }

    const response = await client
      .getSecretValue({ SecretId: process.env.databaseSecret })
      .promise();

    if (response.SecretString == undefined) {
      throw 'Cannot find secret string';
    }

    return JSON.parse(response.SecretString);
  };

  static knexConnection = knex({
    client: 'postgres',
    connection: async () => {
      const secret = await Database.getConnection();

      return {
        host: secret.host,
        port: secret.port,
        user: secret.username,
        password: secret.password,
        database: secret.dbname,
        ssl: true,
      };
    },
});
}

Any guidance on how to solve this issue or even where to start looking would be greatly appreciated.

2 Answers

First of all, it is not a good idea to bypass ssl verification, and doing so can make you vulnerable to various exploits and skips a critical step in the TLS handshake.

What you can do is programmatically download the ca certificate chain bundle from Amazon and place it in the root directory of the lambda along side the handler.

wget https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem -P path/to/handler

Note: you can do this in your buildspec.yaml or in your script that packages the zip file that gets uploaded to aws

Then set the ssl configuration option to the contents of the pem file in your code postgres client configuration, like this:

let pgClient = new postgres.Client({
  user: 'postgres',
  host: 'rds-cluster.cluster-abc.us-west-2.rds.amazonaws.com',
  database: 'mydatabase',
  password: 'postgres',
  port: 5432,
  ssl: {
   ca: fs.readFileSync(path.resolve('rds-combined-ca-bundle.pem'), "utf-8")
  }
})

I know this is old, but just ran into this today. Running with node 10 and an older version of the pg library worked just fine. Updating to node 16 with pg version 8.x caused this error (simplified):

UNABLE_TO_GET_ISSUER_CERT_LOCALLY

In the past, you could indeed just set the ssl parameter to true or 'true' and it would work with the default AWS RDS certificate. Now, it seems we need to at least tell node/pg to ignore the cert verification (since it's self generated).

Using ssl: 'no-verify' works, enabling ssl and telling pg to ignore the verification of the cert chain.

source

UPDATE

For clarity, here's what the connection string would look like. With Knex, the same client info is passed to pg, so it should look similar to a pg client connection.

static knexConnection = knex({
    client: 'postgres',
    connection: async () => {
      const secret = await Database.getConnection();

      return {
        host: secret.host,
        port: secret.port,
        user: secret.username,
        password: secret.password,
        database: secret.dbname,
        ssl: 'no-verify',
      };
    }
Related