diff --git a/CHANGELOG.md b/CHANGELOG.md index 386cf7f..e808d11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Change Log This project adheres to [Semantic Versioning](http://semver.org/). All notable changes will be documented in this file. +## [Unreleased](https://github.com/OldSneerJaw/borealis-pg-cli/compare/v1.1.0...HEAD) +- Support the new secure tunnel connection info config var (`DATABASE_TUNNEL_BPG_CONN_INFO`) +- Require SSL/TLS for DB connections when using the `borealis-pg:run` command + ## [1.1.0](https://github.com/OldSneerJaw/borealis-pg-cli/compare/v1.0.1...v1.1.0) - Adds an add-on status field to the `borealis-pg:info` (alias: `borealis-pg`) command diff --git a/src/commands/borealis-pg/run.test.ts b/src/commands/borealis-pg/run.test.ts index 2aabb18..f0690c3 100644 --- a/src/commands/borealis-pg/run.test.ts +++ b/src/commands/borealis-pg/run.test.ts @@ -60,16 +60,13 @@ const expectedSshHostKey = 'AAAAC3NzaC1lZDI1NTE5AAAAIKkk9uh8+g/gKlLlbi4sVv4VJkia const expectedSshHostKeyEntry = `${expectedSshHostKeyFormat} ${expectedSshHostKey}` const fakeAppConfigVars: {[name: string]: string} = {FOO_BAR: 'baz'} - fakeAppConfigVars[`${fakeAttachmentName}_URL`] = `postgres://${fakePgReadWriteAppUsername}:${fakePgReadWriteAppPassword}@` + `${localPgHostname}:${customPgPort}/${fakePgDbName}` - fakeAppConfigVars[`${fakeAttachmentName}_READONLY_URL`] = `postgres://${fakePgReadonlyAppUsername}:${fakePgReadonlyAppPassword}@` + `${localPgHostname}:${customPgPort}/${fakePgDbName}` - -fakeAppConfigVars[`${fakeAttachmentName}_SSH_TUNNEL_BPG_CONNECTION_INFO`] = +fakeAppConfigVars[`${fakeAttachmentName}_TUNNEL_BPG_CONN_INFO`] = `POSTGRES_WRITER_HOST:=${fakePgWriterHost}|` + `POSTGRES_READER_HOST:=${fakePgReaderHost}|` + `POSTGRES_PORT:=${customPgPort}|` + @@ -84,6 +81,14 @@ fakeAppConfigVars[`${fakeAttachmentName}_SSH_TUNNEL_BPG_CONNECTION_INFO`] = 'SSH_USERNAME:=this-ssh-username-should-be-ignored|' + 'SSH_USER_PRIVATE_KEY:=this-ssh-private-key-should-be-ignored' +const fakeObsoleteAppConfigVars: {[name: string]: string} = {} +fakeObsoleteAppConfigVars[`${fakeAttachmentName}_URL`] = + fakeAppConfigVars[`${fakeAttachmentName}_URL`] +fakeObsoleteAppConfigVars[`${fakeAttachmentName}_READONLY_URL`] = + fakeAppConfigVars[`${fakeAttachmentName}_READONLY_URL`] +fakeObsoleteAppConfigVars[`${fakeAttachmentName}_SSH_TUNNEL_BPG_CONNECTION_INFO`] = + fakeAppConfigVars[`${fakeAttachmentName}_TUNNEL_BPG_CONN_INFO`] + const fakeShellCommand = 'my-cool-shell-command' const fakeDbCommand = 'my-cool-sql-command' @@ -277,7 +282,7 @@ describe('noninteractive run command', () => { }) defaultTestContext - .command(['borealis-pg:run', '-a', fakeHerokuAppName, '--shell-cmd', fakeShellCommand]) + .command(['borealis-pg:run', '--app', fakeHerokuAppName, '--shell-cmd', fakeShellCommand]) .it('executes a shell command without a DB port option', ctx => { executeSshClientListener() @@ -325,7 +330,7 @@ describe('noninteractive run command', () => { expect(ctx.stderr).to.endWith(`${fakeStderrMessage}\n`) - // Check what happens when the child process ends with an exit code + // Check what happens when the child process ends with a non-zero exit code const fakeExitCode = 14 verify(mockChildProcessType.on(anyString(), anyFunction())).once() @@ -408,6 +413,7 @@ describe('noninteractive run command', () => { database: fakePgDbName, user: fakePgReadonlyAppUsername, password: fakePgReadonlyAppPassword, + ssl: {rejectUnauthorized: false}, }))).once() // Check the PG client event listeners @@ -609,6 +615,7 @@ describe('noninteractive run command', () => { database: fakePgDbName, user: fakePgReadonlyAppUsername, password: fakePgReadonlyAppPassword, + ssl: {rejectUnauthorized: false}, }))).once() verify(mockPgClientType.connect()).once() @@ -849,6 +856,46 @@ describe('noninteractive run command', () => { verify(mockTcpSocketType.on('error', anyFunction())).once() }) + baseTestContext + .nock(herokuApiBaseUrl, api => api.get(`/apps/${fakeHerokuAppName}/config-vars`) + .reply(200, fakeObsoleteAppConfigVars)) + .nock( + borealisPgApiBaseUrl, + {reqheaders: {authorization: `Bearer ${fakeHerokuAuthToken}`}}, + api => api.post(`/heroku/resources/${fakeAddonName}/personal-ssh-users`) + .reply( + 200, + { + sshHost: fakeSshHost, + sshPort: customSshPort, + sshUsername: fakeSshUsername, + sshPrivateKey: fakeSshPrivateKey, + publicSshHostKey: expectedSshHostKeyEntry, + })) + .nock(herokuApiBaseUrl, api => mockAddonAttachmentRequests(api)) + .command(['borealis-pg:run', '-a', fakeHerokuAppName, '-e', fakeShellCommand]) + .it('executes a command with the obsolete tunnel connection info config var', () => { + executeSshClientListener() + + verify(mockChildProcessFactoryType.spawn( + fakeShellCommand, + deepEqual({ + env: { + ...tunnelServices.nodeProcess.env, + PGHOST: localPgHostname, + PGPORT: defaultPgPort.toString(), + PGDATABASE: fakePgDbName, + PGUSER: fakePgReadonlyAppUsername, + PGPASSWORD: fakePgReadonlyAppPassword, + DATABASE_URL: + `postgres://${fakePgReadonlyAppUsername}:${fakePgReadonlyAppPassword}@` + + `${localPgHostname}:${defaultPgPort}/${fakePgDbName}`, + }, + shell: true, + stdio: ['ignore', null, null], + }))).once() + }) + test .stdout() .stderr() diff --git a/src/commands/borealis-pg/run.ts b/src/commands/borealis-pg/run.ts index 0329648..15d4200 100644 --- a/src/commands/borealis-pg/run.ts +++ b/src/commands/borealis-pg/run.ts @@ -212,8 +212,14 @@ like pgAdmin).` appName: string, attachmentName: string, enableWriteAccess: boolean): Promise { - const appConnInfoConfigVarName = `${attachmentName}_SSH_TUNNEL_BPG_CONNECTION_INFO` + const newConnInfoConfigVarName = `${attachmentName}_TUNNEL_BPG_CONN_INFO` + const obsoleteConnInfoConfigVarName = `${attachmentName}_SSH_TUNNEL_BPG_CONNECTION_INFO` + const configVarsInfo = await this.heroku.get(`/apps/${appName}/config-vars`) + const appConnInfoConfigVarName = + configVarsInfo.body[newConnInfoConfigVarName] ? + newConnInfoConfigVarName : + obsoleteConnInfoConfigVarName const appConnInfo = configVarsInfo.body[appConnInfoConfigVarName] const dbHostVar = enableWriteAccess ? 'POSTGRES_WRITER_HOST' : 'POSTGRES_READER_HOST' @@ -267,6 +273,7 @@ like pgAdmin).` database: connInfo.db.dbName, user: connInfo.db.dbUsername, password: connInfo.db.dbPassword, + ssl: {rejectUnauthorized: false}, }).on('end', () => { sshClient.end() tunnelServices.nodeProcess.exit()