Skip to content

Commit 5d02521

Browse files
authored
Merge pull request #5 from cruxstack/dev
feat: add submodule to create ssh-tunnels
2 parents e0d17c1 + ec2d07e commit 5d02521

File tree

8 files changed

+541
-3
lines changed

8 files changed

+541
-3
lines changed

modules/teleport-db-login/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ for a secure and reusable way to handle database authentication.
66

77
## Overview
88

9-
This submodule performs the following actions:
10-
119
- Retrieves and sets the database connection information such as host, port, SSL
1210
CA, SSL certificate, and SSL key.
1311
- Executes the `db-login` command of Teleport's CLI to authenticate with the

modules/teleport-db-login/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ locals {
99

1010
data "external" "db_connection_info" {
1111
program = [
12-
"${path.module}/externals/tsh.sh",
12+
"${path.module}/assets/tsh.sh",
1313
"db-login",
1414
"stdin"
1515
]
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Terraform SSH Tunnel
2+
3+
This Terraform submodule creates an SSH tunnel using Teleport. It encapsulates
4+
the logic needed to securely connect to a target host through a specific gateway
5+
within a Teleport cluster.
6+
7+
## Overview
8+
9+
- Sets up an SSH tunnel via Teleport.
10+
- Utilizes the Teleport cluster and gateway to route the tunnel.
11+
- Connects to a specified target host and port.
12+
13+
## Usage
14+
15+
```hcl
16+
module "teleport_ssh_tunnel" {
17+
source = "cruxstack/teleport-cluster/aws//modules/teleport-ssh-tunnel"
18+
version = "x.x.x"
19+
20+
target_cluster = "your-target-cluster.teleport.example.com"
21+
terraform_gateway = "name-of-terraform-gateway"
22+
target_host = "your-target-database.example.com"
23+
target_host = "5439" # example for redshift
24+
}
25+
26+
# configure redshift (eg, `brainly/redshift`) provider to connect to the db
27+
provider "redshift" {
28+
host = module.teleport_ssh_tunnel.host
29+
port = module.teleport_ssh_tunnel.port
30+
database = "<db-name>"
31+
username = "<db-user>"
32+
password = "<db-pass>"
33+
}
34+
```
35+
36+
## Requirements
37+
38+
- Terraform host requires `tsh` and `jq` to be installed.
39+
- Teleport cluster must be preconfigured with the target databases.
40+
- Active login in session is required before using this module.
41+
- eg, `tsh login --proxy=teleport.example.com --user=<user>`
42+
43+
## Inputs
44+
45+
| Name | Description | Type | Default | Required |
46+
|---------------------|--------------------------|----------|---------|:--------:|
47+
| `terraform_cluster` | Teleport cluster domain. | `string` | n/a | yes |
48+
| `terraform_gateway` | Teleport gateway. | `string` | n/a | yes |
49+
| `target_host` | Target host. | `string` | n/a | yes |
50+
| `target_port` | Target port. | `number` | n/a | yes |
51+
52+
## Outputs
53+
54+
| Name | Description |
55+
|--------|------------------|
56+
| `host` | SSH tunnel host. |
57+
| `port` | SSH tunnel port. |
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#!/usr/bin/env bash
2+
# shellcheck disable=SC2317
3+
4+
SCRIPT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/" &> /dev/null && pwd )"
5+
SCRIPT_EXIT_CODE=0
6+
7+
TUNNEL_TIMEOUT=20m
8+
9+
# ---------------------------------------------------------------------- fns ---
10+
11+
function check_process_exists {
12+
ps -p "${1}" >/dev/null 2>&1 && echo "true" || echo "false"
13+
}
14+
15+
function get_random_ephemeral_port() {
16+
LOWER_BOUND=49152
17+
UPPER_BOUND=65000
18+
while true; do
19+
CANDIDATE_PORT=$(( LOWER_BOUND + (RANDOM % (UPPER_BOUND - LOWER_BOUND)) ))
20+
if ! (echo -n "" >/dev/tcp/127.0.0.1/"${CANDIDATE_PORT}") >/dev/null 2>&1; then
21+
# return port number if open
22+
echo "${CANDIDATE_PORT}" && break
23+
fi
24+
done
25+
}
26+
27+
function get_gateway_address() {
28+
TELEPORT_CLUSTER=${1:?}
29+
TELEPORT_GATEWAY_NAME=${2:?}
30+
31+
NODE_HOST=$(
32+
tsh ls --cluster "${TELEPORT_CLUSTER}" \
33+
--query="labels[\"service\"] == \"${TELEPORT_GATEWAY_NAME}\"" \
34+
--format names | head -n 1
35+
)
36+
echo "root@${NODE_HOST}"
37+
}
38+
39+
function open_tunnel() {
40+
TSH_CLUSTER_NAME=${1:?}
41+
TUNNEL_LOCAL_PORT=${2:?}
42+
TUNNEL_TARGET_HOST=${3:?}
43+
TUNNEL_TARGET_PORT=${4:?}
44+
TUNNEL_GATEWAY_ADDRESS=${5:?}
45+
46+
tsh ssh --cluster "${TSH_CLUSTER_NAME}" \
47+
-N -L "${TUNNEL_LOCAL_PORT}:${TUNNEL_TARGET_HOST}:${TUNNEL_TARGET_PORT}" \
48+
"${TUNNEL_GATEWAY_ADDRESS}"
49+
}
50+
51+
function open_background_tunnel() {
52+
TSH_CLUSTER_NAME=${1:?}
53+
TUNNEL_LOCAL_PORT=${2:?}
54+
TUNNEL_TARGET_HOST=${3:?}
55+
TUNNEL_TARGET_PORT=${4:?}
56+
TUNNEL_GATEWAY_ADDRESS=${5:?}
57+
58+
tsh ssh --cluster "${TSH_CLUSTER_NAME}" \
59+
-N -L "${TUNNEL_LOCAL_PORT}:${TUNNEL_TARGET_HOST}:${TUNNEL_TARGET_PORT}" \
60+
"${TUNNEL_GATEWAY_ADDRESS}" &
61+
TUNNEL_PID=$!
62+
63+
sleep 0
64+
65+
while true ; do
66+
if [[ "$(check_process_exists "${TUNNEL_PID}")" == "false" ]] ; then
67+
echo "ERROR: tsh ssh tunnel process (${TUNNEL_PID}) failed" >&2
68+
exit 1
69+
fi
70+
sleep 1
71+
done
72+
73+
kill "${TUNNEL_PID}"
74+
}
75+
76+
function open_background_tunnel_with_timeout() {
77+
TSH_CLUSTER_NAME=${1:?}
78+
TUNNEL_LOCAL_PORT=${2:?}
79+
TUNNEL_TARGET_HOST=${3:?}
80+
TUNNEL_TARGET_PORT=${4:?}
81+
TUNNEL_GATEWAY_ADDRESS=${5:?}
82+
TUNNEL_TIMEOUT=${6:-$TUNNEL_TIMEOUT}
83+
84+
PARENT_PROCESS_ID="$(ps -p "${PPID}" -o "ppid=")"
85+
86+
CHILD_PROCRESS_LOG=$(mktemp)
87+
nohup timeout "${TUNNEL_TIMEOUT}" \
88+
"${SCRIPT_ROOT}/tunneler.sh" \
89+
"open_background_tunnel" \
90+
"${TSH_CLUSTER_NAME}" \
91+
"${TUNNEL_LOCAL_PORT}" \
92+
"${TUNNEL_TARGET_HOST}" \
93+
"${TUNNEL_TARGET_PORT}" \
94+
"${TUNNEL_GATEWAY_ADDRESS}" \
95+
"${PARENT_PROCESS_ID}" \
96+
<&- >&- 2>"${CHILD_PROCRESS_LOG}" &
97+
CHILD_PROCESS_ID=$!
98+
99+
sleep 3
100+
101+
# throw error if tunnel (child process) is not active
102+
if [[ "$(check_process_exists "${CHILD_PROCESS_ID}")" == "false" ]]; then
103+
echo "ERROR: child process (${CHILD_PROCESS_ID}) failed" >&2
104+
cat "${CHILD_PROCRESS_LOG}" >&2
105+
SCRIPT_EXIT_CODE=1
106+
fi
107+
}
108+
109+
# --------------------------------------------------------------------- main ---
110+
111+
function create() {
112+
TELEPORT_CLUSTER=${1:?}
113+
TELEPORT_GATEWAY_NAME=${2:?}
114+
TUNNEL_TARGET_HOST=${3:?}
115+
TUNNEL_TARGET_PORT=${4:?}
116+
117+
TUNNEL_LOCAL_PORT=$(get_random_ephemeral_port)
118+
TUNNEL_GATEWAY_ADDRESS=$(get_gateway_address "${TELEPORT_CLUSTER}" "${TELEPORT_GATEWAY_NAME}")
119+
120+
open_background_tunnel_with_timeout \
121+
"${TELEPORT_CLUSTER}" \
122+
"${TUNNEL_LOCAL_PORT}" \
123+
"${TUNNEL_TARGET_HOST}" \
124+
"${TUNNEL_TARGET_PORT}" \
125+
"${TUNNEL_GATEWAY_ADDRESS}"
126+
127+
echo "${TUNNEL_LOCAL_PORT}"
128+
}
129+
130+
# ------------------------------------------------------------------- script ---
131+
132+
if [[ "${1}" == "create" && "${2}" == "stdin" ]]; then
133+
134+
# handler if input is stdin (e.g. from terraform)
135+
136+
INPUT="$(dd 2>/dev/null)"
137+
TELEPORT_CLUSTER=$(echo "${INPUT}" | jq -r .teleport_cluster)
138+
TELEPORT_GATEWAY_NAME=$(echo "${INPUT}" | jq -r .teleport_gateway_name)
139+
TUNNEL_TARGET_HOST=$(echo "${INPUT}" | jq -r .target_host)
140+
TUNNEL_TARGET_PORT=$(echo "${INPUT}" | jq -r .target_port)
141+
142+
TUNNEL_LOCAL_PORT=$(create "${TELEPORT_CLUSTER}" "${TELEPORT_GATEWAY_NAME}" "${TUNNEL_TARGET_HOST}" "${TUNNEL_TARGET_PORT}")
143+
echo "{\"host\":\"localhost\",\"port\":\"${TUNNEL_LOCAL_PORT}\"}"
144+
145+
elif [[ "${1}" == "create" ]]; then
146+
147+
# handler for normal cli calls
148+
149+
shift
150+
151+
TUNNEL_LOCAL_PORT=$(create "${@}")
152+
echo "localhost:${TUNNEL_LOCAL_PORT}"
153+
154+
else
155+
156+
COMMAND=${1:?}
157+
COMMAND=${COMMAND//-/_} # convert dashes to unders
158+
shift
159+
160+
"${COMMAND}" "${@}"
161+
162+
fi
163+
164+
exit "${SCRIPT_EXIT_CODE}"

0 commit comments

Comments
 (0)