Skip to content

Commit

Permalink
Merge pull request #5 from cruxstack/dev
Browse files Browse the repository at this point in the history
feat: add submodule to create ssh-tunnels
  • Loading branch information
sgtoj authored Aug 9, 2023
2 parents e0d17c1 + ec2d07e commit 5d02521
Show file tree
Hide file tree
Showing 8 changed files with 541 additions and 3 deletions.
2 changes: 0 additions & 2 deletions modules/teleport-db-login/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ for a secure and reusable way to handle database authentication.

## Overview

This submodule performs the following actions:

- Retrieves and sets the database connection information such as host, port, SSL
CA, SSL certificate, and SSL key.
- Executes the `db-login` command of Teleport's CLI to authenticate with the
Expand Down
2 changes: 1 addition & 1 deletion modules/teleport-db-login/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ locals {

data "external" "db_connection_info" {
program = [
"${path.module}/externals/tsh.sh",
"${path.module}/assets/tsh.sh",
"db-login",
"stdin"
]
Expand Down
57 changes: 57 additions & 0 deletions modules/teleport-ssh-tunnel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Terraform SSH Tunnel

This Terraform submodule creates an SSH tunnel using Teleport. It encapsulates
the logic needed to securely connect to a target host through a specific gateway
within a Teleport cluster.

## Overview

- Sets up an SSH tunnel via Teleport.
- Utilizes the Teleport cluster and gateway to route the tunnel.
- Connects to a specified target host and port.

## Usage

```hcl
module "teleport_ssh_tunnel" {
source = "cruxstack/teleport-cluster/aws//modules/teleport-ssh-tunnel"
version = "x.x.x"
target_cluster = "your-target-cluster.teleport.example.com"
terraform_gateway = "name-of-terraform-gateway"
target_host = "your-target-database.example.com"
target_host = "5439" # example for redshift
}
# configure redshift (eg, `brainly/redshift`) provider to connect to the db
provider "redshift" {
host = module.teleport_ssh_tunnel.host
port = module.teleport_ssh_tunnel.port
database = "<db-name>"
username = "<db-user>"
password = "<db-pass>"
}
```

## Requirements

- Terraform host requires `tsh` and `jq` to be installed.
- Teleport cluster must be preconfigured with the target databases.
- Active login in session is required before using this module.
- eg, `tsh login --proxy=teleport.example.com --user=<user>`

## Inputs

| Name | Description | Type | Default | Required |
|---------------------|--------------------------|----------|---------|:--------:|
| `terraform_cluster` | Teleport cluster domain. | `string` | n/a | yes |
| `terraform_gateway` | Teleport gateway. | `string` | n/a | yes |
| `target_host` | Target host. | `string` | n/a | yes |
| `target_port` | Target port. | `number` | n/a | yes |

## Outputs

| Name | Description |
|--------|------------------|
| `host` | SSH tunnel host. |
| `port` | SSH tunnel port. |
164 changes: 164 additions & 0 deletions modules/teleport-ssh-tunnel/assets/tunneler.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/usr/bin/env bash
# shellcheck disable=SC2317

SCRIPT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/" &> /dev/null && pwd )"
SCRIPT_EXIT_CODE=0

TUNNEL_TIMEOUT=20m

# ---------------------------------------------------------------------- fns ---

function check_process_exists {
ps -p "${1}" >/dev/null 2>&1 && echo "true" || echo "false"
}

function get_random_ephemeral_port() {
LOWER_BOUND=49152
UPPER_BOUND=65000
while true; do
CANDIDATE_PORT=$(( LOWER_BOUND + (RANDOM % (UPPER_BOUND - LOWER_BOUND)) ))
if ! (echo -n "" >/dev/tcp/127.0.0.1/"${CANDIDATE_PORT}") >/dev/null 2>&1; then
# return port number if open
echo "${CANDIDATE_PORT}" && break
fi
done
}

function get_gateway_address() {
TELEPORT_CLUSTER=${1:?}
TELEPORT_GATEWAY_NAME=${2:?}

NODE_HOST=$(
tsh ls --cluster "${TELEPORT_CLUSTER}" \
--query="labels[\"service\"] == \"${TELEPORT_GATEWAY_NAME}\"" \
--format names | head -n 1
)
echo "root@${NODE_HOST}"
}

function open_tunnel() {
TSH_CLUSTER_NAME=${1:?}
TUNNEL_LOCAL_PORT=${2:?}
TUNNEL_TARGET_HOST=${3:?}
TUNNEL_TARGET_PORT=${4:?}
TUNNEL_GATEWAY_ADDRESS=${5:?}

tsh ssh --cluster "${TSH_CLUSTER_NAME}" \
-N -L "${TUNNEL_LOCAL_PORT}:${TUNNEL_TARGET_HOST}:${TUNNEL_TARGET_PORT}" \
"${TUNNEL_GATEWAY_ADDRESS}"
}

function open_background_tunnel() {
TSH_CLUSTER_NAME=${1:?}
TUNNEL_LOCAL_PORT=${2:?}
TUNNEL_TARGET_HOST=${3:?}
TUNNEL_TARGET_PORT=${4:?}
TUNNEL_GATEWAY_ADDRESS=${5:?}

tsh ssh --cluster "${TSH_CLUSTER_NAME}" \
-N -L "${TUNNEL_LOCAL_PORT}:${TUNNEL_TARGET_HOST}:${TUNNEL_TARGET_PORT}" \
"${TUNNEL_GATEWAY_ADDRESS}" &
TUNNEL_PID=$!

sleep 0

while true ; do
if [[ "$(check_process_exists "${TUNNEL_PID}")" == "false" ]] ; then
echo "ERROR: tsh ssh tunnel process (${TUNNEL_PID}) failed" >&2
exit 1
fi
sleep 1
done

kill "${TUNNEL_PID}"
}

function open_background_tunnel_with_timeout() {
TSH_CLUSTER_NAME=${1:?}
TUNNEL_LOCAL_PORT=${2:?}
TUNNEL_TARGET_HOST=${3:?}
TUNNEL_TARGET_PORT=${4:?}
TUNNEL_GATEWAY_ADDRESS=${5:?}
TUNNEL_TIMEOUT=${6:-$TUNNEL_TIMEOUT}

PARENT_PROCESS_ID="$(ps -p "${PPID}" -o "ppid=")"

CHILD_PROCRESS_LOG=$(mktemp)
nohup timeout "${TUNNEL_TIMEOUT}" \
"${SCRIPT_ROOT}/tunneler.sh" \
"open_background_tunnel" \
"${TSH_CLUSTER_NAME}" \
"${TUNNEL_LOCAL_PORT}" \
"${TUNNEL_TARGET_HOST}" \
"${TUNNEL_TARGET_PORT}" \
"${TUNNEL_GATEWAY_ADDRESS}" \
"${PARENT_PROCESS_ID}" \
<&- >&- 2>"${CHILD_PROCRESS_LOG}" &
CHILD_PROCESS_ID=$!

sleep 3

# throw error if tunnel (child process) is not active
if [[ "$(check_process_exists "${CHILD_PROCESS_ID}")" == "false" ]]; then
echo "ERROR: child process (${CHILD_PROCESS_ID}) failed" >&2
cat "${CHILD_PROCRESS_LOG}" >&2
SCRIPT_EXIT_CODE=1
fi
}

# --------------------------------------------------------------------- main ---

function create() {
TELEPORT_CLUSTER=${1:?}
TELEPORT_GATEWAY_NAME=${2:?}
TUNNEL_TARGET_HOST=${3:?}
TUNNEL_TARGET_PORT=${4:?}

TUNNEL_LOCAL_PORT=$(get_random_ephemeral_port)
TUNNEL_GATEWAY_ADDRESS=$(get_gateway_address "${TELEPORT_CLUSTER}" "${TELEPORT_GATEWAY_NAME}")

open_background_tunnel_with_timeout \
"${TELEPORT_CLUSTER}" \
"${TUNNEL_LOCAL_PORT}" \
"${TUNNEL_TARGET_HOST}" \
"${TUNNEL_TARGET_PORT}" \
"${TUNNEL_GATEWAY_ADDRESS}"

echo "${TUNNEL_LOCAL_PORT}"
}

# ------------------------------------------------------------------- script ---

if [[ "${1}" == "create" && "${2}" == "stdin" ]]; then

# handler if input is stdin (e.g. from terraform)

INPUT="$(dd 2>/dev/null)"
TELEPORT_CLUSTER=$(echo "${INPUT}" | jq -r .teleport_cluster)
TELEPORT_GATEWAY_NAME=$(echo "${INPUT}" | jq -r .teleport_gateway_name)
TUNNEL_TARGET_HOST=$(echo "${INPUT}" | jq -r .target_host)
TUNNEL_TARGET_PORT=$(echo "${INPUT}" | jq -r .target_port)

TUNNEL_LOCAL_PORT=$(create "${TELEPORT_CLUSTER}" "${TELEPORT_GATEWAY_NAME}" "${TUNNEL_TARGET_HOST}" "${TUNNEL_TARGET_PORT}")
echo "{\"host\":\"localhost\",\"port\":\"${TUNNEL_LOCAL_PORT}\"}"

elif [[ "${1}" == "create" ]]; then

# handler for normal cli calls

shift

TUNNEL_LOCAL_PORT=$(create "${@}")
echo "localhost:${TUNNEL_LOCAL_PORT}"

else

COMMAND=${1:?}
COMMAND=${COMMAND//-/_} # convert dashes to unders
shift

"${COMMAND}" "${@}"

fi

exit "${SCRIPT_EXIT_CODE}"
Loading

0 comments on commit 5d02521

Please sign in to comment.