1 | 'use strict'
|
2 |
|
3 | const debug = require('./debug')
|
4 | const tunnel = require('tunnel-ssh')
|
5 | const cli = require('heroku-cli-util')
|
6 | const host = require('./host')
|
7 |
|
8 | const getBastion = function (config, baseName) {
|
9 | const { sample } = require('lodash')
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | const bastionKey = config[`${baseName}_BASTION_KEY`]
|
20 | const bastionHost = sample((config[`${baseName}_BASTIONS`] || '').split(','))
|
21 | return (!(bastionKey && bastionHost))
|
22 | ? {}
|
23 | : { bastionHost, bastionKey }
|
24 | }
|
25 |
|
26 | exports.getBastion = getBastion
|
27 |
|
28 | const env = function (db) {
|
29 | let baseEnv = Object.assign({
|
30 | PGAPPNAME: 'psql non-interactive',
|
31 | PGSSLMODE: (!db.hostname || db.hostname === 'localhost') ? 'prefer' : 'require'
|
32 | }, process.env)
|
33 | let mapping = {
|
34 | PGUSER: 'user',
|
35 | PGPASSWORD: 'password',
|
36 | PGDATABASE: 'database',
|
37 | PGPORT: 'port',
|
38 | PGHOST: 'host'
|
39 | }
|
40 | Object.keys(mapping).forEach((envVar) => {
|
41 | let val = db[mapping[envVar]]
|
42 | if (val) {
|
43 | baseEnv[envVar] = val
|
44 | }
|
45 | })
|
46 | return baseEnv
|
47 | }
|
48 |
|
49 | exports.env = env
|
50 |
|
51 | function tunnelConfig (db) {
|
52 | const localHost = '127.0.0.1'
|
53 | const localPort = Math.floor(Math.random() * (65535 - 49152) + 49152)
|
54 | return {
|
55 | username: 'bastion',
|
56 | host: db.bastionHost,
|
57 | privateKey: db.bastionKey,
|
58 | dstHost: db.host,
|
59 | dstPort: db.port,
|
60 | localHost: localHost,
|
61 | localPort: localPort
|
62 | }
|
63 | }
|
64 |
|
65 | exports.tunnelConfig = tunnelConfig
|
66 |
|
67 | function getConfigs (db) {
|
68 | let dbEnv = env(db)
|
69 | const dbTunnelConfig = tunnelConfig(db)
|
70 | if (db.bastionKey) {
|
71 | dbEnv = Object.assign(dbEnv, {
|
72 | PGPORT: dbTunnelConfig.localPort,
|
73 | PGHOST: dbTunnelConfig.localHost
|
74 | })
|
75 | }
|
76 | return {
|
77 | dbEnv: dbEnv,
|
78 | dbTunnelConfig: dbTunnelConfig
|
79 | }
|
80 | }
|
81 |
|
82 | exports.getConfigs = getConfigs
|
83 |
|
84 | function sshTunnel (db, dbTunnelConfig, timeout) {
|
85 | return new Promise((resolve, reject) => {
|
86 |
|
87 |
|
88 | let timer = setTimeout(() => reject(new Error('Establishing a secure tunnel timed out')), timeout)
|
89 | if (db.bastionKey) {
|
90 | let tun = tunnel(dbTunnelConfig, (err, tnl) => {
|
91 | if (err) {
|
92 | debug(err)
|
93 | reject(new Error('Unable to establish a secure tunnel to your database.'))
|
94 | }
|
95 | debug('Tunnel created')
|
96 | clearTimeout(timer)
|
97 | resolve(tnl)
|
98 | })
|
99 | tun.on('error', (err) => {
|
100 |
|
101 | debug(err)
|
102 | cli.exit(1, 'Secure tunnel to your database failed')
|
103 | })
|
104 | } else {
|
105 | clearTimeout(timer)
|
106 | resolve()
|
107 | }
|
108 | })
|
109 | }
|
110 |
|
111 | exports.sshTunnel = sshTunnel
|
112 |
|
113 | function * fetchConfig (heroku, db) {
|
114 | return yield heroku.get(
|
115 | `/client/v11/databases/${encodeURIComponent(db.id)}/bastion`,
|
116 | {
|
117 | host: host(db)
|
118 | }
|
119 | )
|
120 | }
|
121 |
|
122 | exports.fetchConfig = fetchConfig
|