rds_ssm_connect 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/connect.js +106 -117
- package/package.json +9 -6
package/connect.js
CHANGED
|
@@ -1,137 +1,126 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
3
|
+
import inquirer from 'inquirer'
|
|
4
|
+
import fs from 'fs/promises'
|
|
5
|
+
import os from 'os'
|
|
6
|
+
import path from 'path'
|
|
7
|
+
import { exec } from 'child_process'
|
|
8
|
+
import { promisify } from 'util'
|
|
9
9
|
import { envPortMapping, REGION, TABLE_NAME } from './envPortMapping.js'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
11
|
+
const execAsync = promisify(exec)
|
|
12
|
+
|
|
13
|
+
async function readAwsConfig () {
|
|
14
|
+
const awsConfigPath = path.join(os.homedir(), '.aws', 'config')
|
|
15
|
+
try {
|
|
16
|
+
const awsConfig = await fs.readFile(awsConfigPath, 'utf-8')
|
|
17
|
+
return awsConfig
|
|
18
|
+
.split('\n')
|
|
19
|
+
.filter(line => line.startsWith('[') && line.endsWith(']'))
|
|
20
|
+
.map(line => line.slice(1, -1))
|
|
21
|
+
.map(line => line.replace('profile ', ''))
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('Error reading AWS config file:', error)
|
|
24
|
+
return []
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function runCommand (command) {
|
|
29
|
+
try {
|
|
30
|
+
const { stdout } = await execAsync(command)
|
|
31
|
+
return stdout.trim()
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(`Error executing command: ${command}`)
|
|
34
|
+
console.error(error)
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main () {
|
|
40
|
+
try {
|
|
41
|
+
const ENVS = await readAwsConfig()
|
|
42
|
+
|
|
43
|
+
if (ENVS.length === 0) {
|
|
44
|
+
console.error('No environments found in AWS config file.')
|
|
45
|
+
return
|
|
32
46
|
}
|
|
33
|
-
])
|
|
34
|
-
.then((answers) => {
|
|
35
|
-
const ENV = answers.ENV // Get the selected environment from the user's answers
|
|
36
|
-
console.log(`You selected: ${ENV}`)
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
const answers = await inquirer.prompt([
|
|
49
|
+
{
|
|
50
|
+
type: 'list',
|
|
51
|
+
name: 'ENV',
|
|
52
|
+
message: 'Please select the environment:',
|
|
53
|
+
choices: ENVS
|
|
54
|
+
}
|
|
55
|
+
])
|
|
40
56
|
|
|
41
|
-
|
|
57
|
+
const ENV = answers.ENV
|
|
58
|
+
const allEnvSuffixes = Object.keys(envPortMapping).sort((a, b) => b.length - a.length)
|
|
42
59
|
const matchedSuffix = allEnvSuffixes.find(suffix => ENV.endsWith(suffix))
|
|
60
|
+
const portNumber = envPortMapping[matchedSuffix] || '5432'
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
const secretsListCommand = `aws-vault exec ${ENV} -- aws secretsmanager list-secrets --region ${REGION} --query "SecretList[?starts_with(Name, 'rds!cluster')].Name | [0]" --output text`
|
|
63
|
+
const SECRET_NAME = await runCommand(secretsListCommand)
|
|
46
64
|
|
|
47
|
-
if (!
|
|
48
|
-
console.error(
|
|
49
|
-
|
|
65
|
+
if (!SECRET_NAME) {
|
|
66
|
+
console.error('No secret found with name starting with rds!cluster.')
|
|
67
|
+
return
|
|
50
68
|
}
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
70
|
+
const secretsGetCommand = `aws-vault exec ${ENV} -- aws secretsmanager get-secret-value --region ${REGION} --secret-id "${SECRET_NAME}" --query SecretString --output text`
|
|
71
|
+
const secretString = await runCommand(secretsGetCommand)
|
|
72
|
+
const CREDENTIALS = JSON.parse(secretString)
|
|
73
|
+
const USERNAME = CREDENTIALS.username
|
|
74
|
+
const PASSWORD = CREDENTIALS.password
|
|
55
75
|
|
|
56
|
-
|
|
57
|
-
|
|
76
|
+
console.log(`Your connection string is: psql -h localhost -p ${portNumber} -U ${USERNAME} -d ${TABLE_NAME}`)
|
|
77
|
+
console.log(`Use the password: ${PASSWORD}`)
|
|
58
78
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const SECRET_NAME = data.toString().trim()
|
|
79
|
+
const instanceIdCommand = `aws-vault exec ${ENV} -- aws ec2 describe-instances --region ${REGION} --filters "Name=tag:Name,Values='*bastion*'" "Name=instance-state-name,Values=running" --query "Reservations[].Instances[].[InstanceId] | [0][0]" --output text`
|
|
80
|
+
const INSTANCE_ID = await runCommand(instanceIdCommand)
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
if (!INSTANCE_ID) {
|
|
83
|
+
console.error('Failed to find a running instance with tag Name=*bastion*.')
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const rdsEndpointCommand = `aws-vault exec ${ENV} -- aws rds describe-db-clusters --region ${REGION} --query "DBClusters[?Status=='available' && ends_with(DBClusterIdentifier, '-rds-aurora')].Endpoint | [0]" --output text`
|
|
88
|
+
const RDS_ENDPOINT = await runCommand(rdsEndpointCommand)
|
|
67
89
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
console.log(`Use the password: ${PASSWORD}`)
|
|
81
|
-
|
|
82
|
-
// Get the ID of the bastion instance in running state
|
|
83
|
-
const instanceIdCommand = `aws ec2 describe-instances --region ${REGION} --filters "Name=tag:Name,Values='*bastion*'" "Name=instance-state-name,Values=running" --query "Reservations[].Instances[].[InstanceId]" --output text`
|
|
84
|
-
const instanceIdProcess = spawn('sh', ['-c', `${awsVaultExecCommand.join(' ')} ${instanceIdCommand}`])
|
|
85
|
-
|
|
86
|
-
instanceIdProcess.stdout.on('data', (data) => {
|
|
87
|
-
const INSTANCE_ID = data.toString().trim()
|
|
88
|
-
|
|
89
|
-
if (!INSTANCE_ID) {
|
|
90
|
-
console.error('Failed to find a running instance with tag Name=*bastion*.')
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Get the endpoint of the RDS cluster
|
|
95
|
-
const rdsEndpointCommand = `aws rds describe-db-clusters --region ${REGION} --query "DBClusters[?Status=='available' && ends_with(DBClusterIdentifier, '-rds-aurora')].Endpoint" --output text`
|
|
96
|
-
const rdsEndpointProcess = spawn('sh', ['-c', `${awsVaultExecCommand.join(' ')} ${rdsEndpointCommand}`])
|
|
97
|
-
|
|
98
|
-
rdsEndpointProcess.stdout.on('data', (data) => {
|
|
99
|
-
const RDS_ENDPOINT = data.toString().trim()
|
|
100
|
-
|
|
101
|
-
if (!RDS_ENDPOINT) {
|
|
102
|
-
console.error('Failed to find the RDS endpoint.')
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Start a port forwarding session to the RDS cluster
|
|
107
|
-
const portForwardingCommand = `aws ssm start-session --target ${INSTANCE_ID} --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters "host=${RDS_ENDPOINT},portNumber='5432',localPortNumber='${portNumber}'" --cli-connect-timeout 0`
|
|
108
|
-
const portForwardingProcess = spawn('sh', ['-c', `${awsVaultExecCommand.join(' ')} ${portForwardingCommand}`])
|
|
109
|
-
|
|
110
|
-
portForwardingProcess.stdout.on('data', (data) => {
|
|
111
|
-
console.log(data.toString().trim())
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
portForwardingProcess.stderr.on('data', (data) => {
|
|
115
|
-
console.error(`Command execution error: ${data.toString()}`)
|
|
116
|
-
})
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
rdsEndpointProcess.stderr.on('data', (data) => {
|
|
120
|
-
console.error(`Command execution error: ${data.toString()}`)
|
|
121
|
-
})
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
instanceIdProcess.stderr.on('data', (data) => {
|
|
125
|
-
console.error(`Command execution error: ${data.toString()}`)
|
|
126
|
-
})
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
secretsGetProcess.stderr.on('data', (data) => {
|
|
130
|
-
console.error(`Command execution error: ${data.toString()}`)
|
|
131
|
-
})
|
|
90
|
+
if (!RDS_ENDPOINT) {
|
|
91
|
+
console.error('Failed to find the RDS endpoint.')
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const portForwardingCommand = `aws-vault exec ${ENV} -- aws ssm start-session --target ${INSTANCE_ID} --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters "host=${RDS_ENDPOINT},portNumber='5432',localPortNumber='${portNumber}'" --cli-connect-timeout 0`
|
|
96
|
+
|
|
97
|
+
console.log('Starting port forwarding session...')
|
|
98
|
+
const child = exec(portForwardingCommand)
|
|
99
|
+
|
|
100
|
+
child.stdout.on('data', (data) => {
|
|
101
|
+
console.log(data.toString())
|
|
132
102
|
})
|
|
133
103
|
|
|
134
|
-
|
|
135
|
-
console.error(
|
|
104
|
+
child.stderr.on('data', (data) => {
|
|
105
|
+
console.error(data.toString())
|
|
136
106
|
})
|
|
107
|
+
|
|
108
|
+
child.on('close', (code) => {
|
|
109
|
+
console.log(`Port forwarding session ended with code ${code}`)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
console.log('Port forwarding session established. You can now connect to the database using the provided connection string.')
|
|
113
|
+
console.log('Press Ctrl+C to end the session.')
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error(`Error: ${error.message}`)
|
|
116
|
+
throw error // Re-throw the error to be caught by the outer catch block
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
main().catch(error => {
|
|
121
|
+
console.error('Unhandled error in main function:', error)
|
|
122
|
+
console.error('Exiting due to unhandled error')
|
|
123
|
+
setImmediate(() => {
|
|
124
|
+
throw new Error('Forcing exit due to unhandled error')
|
|
137
125
|
})
|
|
126
|
+
})
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rds_ssm_connect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@aws-sdk/client-ec2": "^3.
|
|
7
|
-
"@aws-sdk/client-rds": "^3.
|
|
8
|
-
"@aws-sdk/client-ssm": "^3.
|
|
9
|
-
"
|
|
6
|
+
"@aws-sdk/client-ec2": "^3.632.0",
|
|
7
|
+
"@aws-sdk/client-rds": "^3.632.0",
|
|
8
|
+
"@aws-sdk/client-ssm": "^3.632.0",
|
|
9
|
+
"glob": "^11.0.0",
|
|
10
|
+
"inquirer": "^8.2.6",
|
|
11
|
+
"process": "^0.11.10",
|
|
12
|
+
"rimraf": "^6.0.1"
|
|
10
13
|
},
|
|
11
14
|
"bin": {
|
|
12
15
|
"rds_ssm_connect": "./connect.js"
|
|
@@ -16,7 +19,7 @@
|
|
|
16
19
|
"lint:fix": "eslint . --fix"
|
|
17
20
|
},
|
|
18
21
|
"devDependencies": {
|
|
19
|
-
"eslint": "^8.
|
|
22
|
+
"eslint": "^8.57.0",
|
|
20
23
|
"eslint-config-standard": "^17.1.0",
|
|
21
24
|
"eslint-plugin-import": "^2.29.1",
|
|
22
25
|
"eslint-plugin-n": "^16.6.2",
|