From cbe139117aa300fdf4847a3dbadfb08a11175897 Mon Sep 17 00:00:00 2001 From: Justin Downing Date: Wed, 19 Aug 2026 09:45:39 -0700 Subject: [PATCH 1/2] feat: add data pg get-ca command --- src/commands/data/pg/get-ca.ts | 66 ++++++++++++++ .../unit/commands/data/pg/get-ca.unit.test.ts | 85 +++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 src/commands/data/pg/get-ca.ts create mode 100644 test/unit/commands/data/pg/get-ca.unit.test.ts diff --git a/src/commands/data/pg/get-ca.ts b/src/commands/data/pg/get-ca.ts new file mode 100644 index 0000000000..788eed1ea6 --- /dev/null +++ b/src/commands/data/pg/get-ca.ts @@ -0,0 +1,66 @@ +import {Command, flags} from '@heroku-cli/command' +import * as Heroku from '@heroku-cli/schema' +import fs from 'fs-extra' +import fetch from 'node-fetch' +import os from 'node:os' +import path from 'node:path' + +const RDS_CERTIFICATE_HOST = 'https://truststore.pki.rds.amazonaws.com' + +export default class DataPgGetCa extends Command { + static description = 'download the RDS CA bundle for a Heroku region' + static examples = [ + '<%= config.bin %> <%= command.id %> --region virginia', + '<%= config.bin %> <%= command.id %> --region global', + ] + static flags = { + region: flags.string({ + description: 'Heroku region or global for the AWS global CA bundle', + required: true, + }), + } + + public async awsRegion(herokuRegion: string): Promise { + const {body: regions} = await this.heroku.get('/regions') + const region = regions.find(candidate => candidate.name === herokuRegion) + const awsRegion = region?.provider?.region + + if (!awsRegion) throw new Error(`${herokuRegion} is not a Heroku region backed by AWS.`) + + return awsRegion + } + + public destinationDirectory(): string { + if (process.platform === 'win32') { + if (!process.env.APPDATA) throw new Error('APPDATA is not set; unable to determine the PostgreSQL certificate directory.') + + return path.join(process.env.APPDATA, 'postgresql') + } + + return path.join(os.homedir(), '.postgres') + } + + public async download(url: string): Promise { + const response = await fetch(url) + if (!response.ok) throw new Error(`AWS RDS returned ${response.status} ${response.statusText}.`) + + return response.buffer() + } + + public async run(): Promise { + const {flags} = await this.parse(DataPgGetCa) + const awsRegion = flags.region === 'global' ? 'global' : await this.awsRegion(flags.region) + const fileName = `${awsRegion}-bundle.pem` + const destination = path.join(this.destinationDirectory(), fileName) + const url = `${RDS_CERTIFICATE_HOST}/${awsRegion}/${fileName}` + + try { + const certificate = await this.download(url) + await fs.outputFile(destination, certificate, {mode: 0o600}) + this.log(`RDS CA bundle retrieved successfully: ${destination}`) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + this.error(`Unable to retrieve the RDS CA bundle at ${destination}: ${message}`) + } + } +} diff --git a/test/unit/commands/data/pg/get-ca.unit.test.ts b/test/unit/commands/data/pg/get-ca.unit.test.ts new file mode 100644 index 0000000000..df6e54cb5d --- /dev/null +++ b/test/unit/commands/data/pg/get-ca.unit.test.ts @@ -0,0 +1,85 @@ +import {runCommand} from '@heroku-cli/test-utils' +import {expect} from 'chai' +import fs from 'fs-extra' +import { + afterEach, + beforeEach, + describe, + it, +} from 'mocha' +import nock from 'nock' +import {restore, SinonStub, stub} from 'sinon' + +import DataPgGetCa from '../../../../../src/commands/data/pg/get-ca.js' + +describe('data:pg:get-ca', function () { + const certificate = '-----BEGIN CERTIFICATE-----\ncertificate\n-----END CERTIFICATE-----\n' + const destinationDirectory = '/tmp/postgres' + let outputFileStub: SinonStub + + beforeEach(function () { + stub(DataPgGetCa.prototype, 'destinationDirectory').returns(destinationDirectory) + outputFileStub = stub(fs, 'outputFile').resolves() + }) + + afterEach(function () { + restore() + nock.cleanAll() + }) + + it('downloads the CA bundle for an AWS-backed Heroku region', async function () { + nock('https://api.heroku.com') + .get('/regions') + .reply(200, [{name: 'virginia', provider: {region: 'us-east-1'}}]) + nock('https://truststore.pki.rds.amazonaws.com') + .get('/us-east-1/us-east-1-bundle.pem') + .reply(200, certificate) + + const {stdout} = await runCommand(DataPgGetCa, ['--region', 'virginia']) + + expect(outputFileStub.calledOnceWith( + '/tmp/postgres/us-east-1-bundle.pem', + Buffer.from(certificate), + {mode: 0o600}, + )).to.be.true + expect(stdout).to.equal('RDS CA bundle retrieved successfully: /tmp/postgres/us-east-1-bundle.pem\n') + }) + + it('downloads the AWS global CA bundle without requesting Heroku regions', async function () { + nock('https://truststore.pki.rds.amazonaws.com') + .get('/global/global-bundle.pem') + .reply(200, certificate) + + const {stdout} = await runCommand(DataPgGetCa, ['--region', 'global']) + + expect(outputFileStub.calledOnceWith( + '/tmp/postgres/global-bundle.pem', + Buffer.from(certificate), + {mode: 0o600}, + )).to.be.true + expect(stdout).to.equal('RDS CA bundle retrieved successfully: /tmp/postgres/global-bundle.pem\n') + }) + + it('denies retrieval when AWS cannot provide the CA bundle', async function () { + nock('https://api.heroku.com') + .get('/regions') + .reply(200, [{name: 'virginia', provider: {region: 'us-east-1'}}]) + nock('https://truststore.pki.rds.amazonaws.com') + .get('/us-east-1/us-east-1-bundle.pem') + .reply(503, 'temporarily unavailable') + + const error = 'Unable to retrieve the RDS CA bundle at /tmp/postgres/us-east-1-bundle.pem: AWS RDS returned 503 Service Unavailable.' + const {error: commandError} = await runCommand(DataPgGetCa, ['--region', 'virginia']) + expect(commandError?.message).to.equal(error) + expect(outputFileStub.called).to.be.false + }) + + it('rejects non-AWS Heroku regions', async function () { + nock('https://api.heroku.com') + .get('/regions') + .reply(200, [{name: 'example', provider: {region: null}}]) + + const {error} = await runCommand(DataPgGetCa, ['--region', 'example']) + expect(error?.message).to.equal('example is not a Heroku region backed by AWS.') + }) +}) From c961c34747ea0d05d1105bfa003765357a609559 Mon Sep 17 00:00:00 2001 From: Justin Downing Date: Wed, 19 Aug 2026 12:24:58 -0700 Subject: [PATCH 2/2] add support for common runtime region alias names --- src/commands/data/pg/get-ca.ts | 6 ++++ .../unit/commands/data/pg/get-ca.unit.test.ts | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/commands/data/pg/get-ca.ts b/src/commands/data/pg/get-ca.ts index 788eed1ea6..3bac898546 100644 --- a/src/commands/data/pg/get-ca.ts +++ b/src/commands/data/pg/get-ca.ts @@ -6,6 +6,10 @@ import os from 'node:os' import path from 'node:path' const RDS_CERTIFICATE_HOST = 'https://truststore.pki.rds.amazonaws.com' +const REGION_ALIASES: Record = { + eu: 'eu-west-1', + us: 'us-east-1', +} export default class DataPgGetCa extends Command { static description = 'download the RDS CA bundle for a Heroku region' @@ -21,6 +25,8 @@ export default class DataPgGetCa extends Command { } public async awsRegion(herokuRegion: string): Promise { + if (REGION_ALIASES[herokuRegion]) return REGION_ALIASES[herokuRegion] + const {body: regions} = await this.heroku.get('/regions') const region = regions.find(candidate => candidate.name === herokuRegion) const awsRegion = region?.provider?.region diff --git a/test/unit/commands/data/pg/get-ca.unit.test.ts b/test/unit/commands/data/pg/get-ca.unit.test.ts index df6e54cb5d..ca7a83234a 100644 --- a/test/unit/commands/data/pg/get-ca.unit.test.ts +++ b/test/unit/commands/data/pg/get-ca.unit.test.ts @@ -60,6 +60,36 @@ describe('data:pg:get-ca', function () { expect(stdout).to.equal('RDS CA bundle retrieved successfully: /tmp/postgres/global-bundle.pem\n') }) + it('downloads the US Common Runtime CA bundle without requesting Heroku regions', async function () { + nock('https://truststore.pki.rds.amazonaws.com') + .get('/us-east-1/us-east-1-bundle.pem') + .reply(200, certificate) + + const {stdout} = await runCommand(DataPgGetCa, ['--region', 'us']) + + expect(outputFileStub.calledOnceWith( + '/tmp/postgres/us-east-1-bundle.pem', + Buffer.from(certificate), + {mode: 0o600}, + )).to.be.true + expect(stdout).to.equal('RDS CA bundle retrieved successfully: /tmp/postgres/us-east-1-bundle.pem\n') + }) + + it('downloads the EU Common Runtime CA bundle without requesting Heroku regions', async function () { + nock('https://truststore.pki.rds.amazonaws.com') + .get('/eu-west-1/eu-west-1-bundle.pem') + .reply(200, certificate) + + const {stdout} = await runCommand(DataPgGetCa, ['--region', 'eu']) + + expect(outputFileStub.calledOnceWith( + '/tmp/postgres/eu-west-1-bundle.pem', + Buffer.from(certificate), + {mode: 0o600}, + )).to.be.true + expect(stdout).to.equal('RDS CA bundle retrieved successfully: /tmp/postgres/eu-west-1-bundle.pem\n') + }) + it('denies retrieval when AWS cannot provide the CA bundle', async function () { nock('https://api.heroku.com') .get('/regions')