diff --git a/README.md b/README.md index dfd9aed..ccb68bd 100755 --- a/README.md +++ b/README.md @@ -1,46 +1,47 @@ -This terraform module allows to communicate with a resource via a tunnel. A tunnel is a -gateway providing a bidirectionnal connection between a 'public' area and a 'private' +This terraform module allows to communicate with a resource via a tunnel. A tunnel (aka gateway, aka bastion host) +provides a bidirectionnal connection between a 'public' area and a 'private' area. Terraform runs on a host located in the 'public ' area and uses the gateway to access a target host & port located in the 'private' area. -It is used, for instance, to create and configure databases on AWS RDS instances. +This is used, for instance, to create and configure databases on AWS RDS instances. Creating RDS instances is easy, as it uses the public AWS API, but creating -databases is more complex because it requires connecting to the RDS instance which is, -usually, accessible from private subnets only. -Running terraform on a host inside the private area is a solution but it is quite complex -to install and manage. - -An alternate solution is to access remote resources via -a tunnel (aka 'bastion host'). The steps are : - -- You open a secure connection from your client to a gateway host (using the gateway's - public IP address). -- and, then, the gateway host opens a connection to the target host/port (using - private subnets). - -Initially, only SSH tunnels were supported, hence the module name. We now support -the following gateways : -- [SSH tunnel](https://www.ssh.com/academy/ssh/tunneling-example) +databases is more complex because it requires connecting to the RDS instance which, +usually, is accessible from private subnets only. + +Running terraform on a host inside the private area is a possible solution but generally too complex +to install and manage. Opening a temporary tunnel to the target is much easier. + +Initially, only SSH tunnels were supported, hence the module name. We now support : +- [SSH tunnels](https://www.ssh.com/academy/ssh/tunneling-example) - [AWS Systems Manager (SSM)](https://docs.aws.amazon.com/systems-manager/latest/userguide/) +- [Google IAP](https://cloud.google.com/iap/docs/using-tcp-forwarding) - [Kubernetes port forwarding](https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/) You can also provide your own shell script if your gateway is not supported yet. +Note that, except for bare SSH tunnels, other gateways were introduced in v 2.0.0 and may not +be suitable for production use yet. That's why your help is greatly appreciated to test and validate them. + --- - * [Tunnel conditional creation](#tunnel-conditional-creation) - * [Target host name resolution](#target-host-name-resolution) - * [Environment](#environment) - * [Available gateways](#available-gateways) + * [Supported gateways](#supported-gateways) * [SSH](#ssh) - * [Multiple SSH gateways](#multiple-ssh-gateways) + * [Using multiple SSH gateways (ProxyJump)](#using-multiple-ssh-gateways-proxyjump) + * [Target host name resolution](#target-host-name-resolution) * [AWS SSM](#aws-ssm) + * [Goggle IAP](#goggle-iap) * [Kubernetes port forwarding](#kubernetes-port-forwarding) * [External](#external) + * [Tunnel conditional creation](#tunnel-conditional-creation) + * [Environment](#environment) * [Requirements](#requirements) * [Posix shell](#posix-shell) * [timeout](#timeout) * [nohup](#nohup) + * [SSH client](#ssh-client) + * [AWS CLI](#aws-cli) + * [Kubectl](#kubectl) + * [gcloud CLI](#gcloud-cli) * [Limitations](#limitations) * [Running terraform apply from plan out](#running-terraform-apply-from-plan-out) * [Examples](#examples) @@ -52,37 +53,11 @@ You can also provide your own shell script if your gateway is not supported yet. * [Outputs](#outputs) - + -## Tunnel conditional creation - -When the 'create' input variable is false or the 'gateway_host' -input variable is an empty string, no tunnel -is created and the target host and port are returned as outputs, causing a direct connection -to the target. - -This makes the module usable in a scenario where the fact that the connection -requires a tunnel cannot be determined in advance and depends on input parameters. - -## Target host name resolution - -When supplying the target DNS name, the name is resolved by the -bastion host, not by the client you're running terraform on. So, you can use a private -DNS name, like 'xxxx.csdfkzpf0iww.eu-west-1.rds.amazonaws.com' -without having to convert it to an IP address first. - -## Environment - -The SSH client process inherits your environment, including a -possible SSH agent configuration to retrieve your private key. - -If you to set and/or replace environment variables before creating the tunnel, -you can provide a non-empty 'env' input variable. This string will be 'eval'uated -before launching the command to create the tunnel. When using an SSM connection, for instance, it can be used to set the 'AWS_PROFILE' variable. - -## Available gateways +## Supported gateways ### SSH @@ -90,11 +65,11 @@ Initially, it was the only supported gateway. It remains the default. In order to use this gateway, you need to have a bastion host running some SSH server software. This host must be directly accessible from the host running terraform. It must also have a direct access to the -target host/port. +target host & port. You cannot use passwords to open the SSH connection. So, every potential user must be registered on the -bastion host along with the appropriate public key. In order to avoid this key management, you can also -share a key between your users but keeping a shared secret secure is a complex task. +bastion host along with the appropriate public key. In order to avoid this key management, an alternative +is to share a key between authorized users but keeping a shared secret secure is also a complex task. By default, the module uses the 'ssh -o StrictHostKeyChecking=no' string to launch the SSH client. If you use a different SSH client name/path or @@ -102,18 +77,25 @@ if you want to add/remove options, you can modify this string by setting the 'ssh_cmd' input variable. This may be used, for instance, to add a '-i' option and set the private key to use. -#### Multiple SSH gateways +#### Using multiple SSH gateways (ProxyJump) -Please also note that the module cannot +Please note that the module cannot be used to create a tunnel running through a set of several SSH gateways, each one opening an SSH connection to the next one. This is technically possible using the 'ProxyJump' feature introduced in OpenSSH v7.3 and the feature may be added in a future version, depending on user's demand (I personally don't need it yet). +#### Target host name resolution + +Note that, when supplying a target DNS name, the name is resolved by the +bastion host, not by the client you're running terraform on. So, you can use a private +DNS name, like 'xxxx.csdfkzpf0iww.eu-west-1.rds.amazonaws.com' +without having to convert it to an IP address first. + ### AWS SSM -This feature is still alpha (it was introduced in v 1.14.0). So, don't hesitate to +This feature is still alpha (it was introduced in v 2.0.0). So, don't hesitate to report any good or bad experience using it. Suggestions are welcome too. The feature is adapted from the [terraform-ssm-tunnel](https://github.com/littlejo/terraform-ssm-tunnel/tree/master) fork by [Joseph Ligier](https://github.com/littlejo). Many thanks to Joseph for this addition. @@ -121,38 +103,55 @@ The feature is adapted from the [terraform-ssm-tunnel](https://github.com/little If you're using AWS, you can open a tunnel using AWS SSM ([AWS Systems manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/)). In this case, a 'Session Manager SSH session' is opened from your desktop -to an EC2 instance via the AWS API. This seesion is, then, connected with another +to an EC2 instance via the AWS API. This session is, then, connected with another connection to the target host. -The benefits of using SSM, compared to a bare SSH bastion host, are : +The benefits of using SSM, compared to a bare SSH tunnel, are : - you don't need to maintain a bastion host anymore, -- you don't need to expose SSH ports on your gateway host, as the connection is done via the much safer AWS API public endpoints, -- permissions can be managed per user/group using IAM roles and policies +- you still need an EC2 instance but this instance does not have to expose any port, as the connection is done via the much safer AWS API public endpoints, +- permissions can be managed per user/group using IAM roles and policies (no more public key management). -Note that, since all traffic goes through the AWS API, this can be used on a platform without any inbound access to an EC2 instance (even without public subnets). +Note that, since all traffic goes through the AWS API, this can be used on a fully-private platform (a platform without public subnets). Of course, this requires [the AWS SSM agent to be installed and configured correctly on the EC2 gateway](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html#sessions-start-ssh). SSM agent version must be 2.3.672.0 or later. How to activate the SSM variant : -- Configure the EC2 gateway and define an appropriate policy -- Configure security groups so that the EC2 gateway can access the target host, +- Configure the EC2 gateway and attach appropriate policies +- Configure security groups so that the EC2 gateway can access the target host & port, - set 'type' to 'ssm' -- set 'gateway_host' to the 'instance ID' of the gateway host +- set 'gateway_host' to the instance ID of the EC2 gateway - set 'gateway_user' to the appropriate name (see documentation), generally 'ec2-user' ('ubuntu' when using Ubuntu-based AMIs). -- if needed, set the AWS_PROFILE environment variable using the 'env' input var. +- As an option, add environment variables, like 'AWS_PROFILE', into + the 'env' input array. + +### Goggle IAP + +This feature is EXPERIMENTAL and was never tested. You use it under your total responsibility. +No assumption should be made on the security level it provides. + +That's why, if you try it, we really need your return, +even if (especially if) you meet issues. Please register issues about your experience with this gateway, even +if everything went fine in your environment. This gateway cannot get out its experimental status without your help. + +This gateway uses the Google Identity-Aware Proxy (IAP) feature to create a tunnel to a private target host and port. + +Functionally, the mechanism is quite similar to AWS SSM as it requires an existing Google VM instance to be used as gateway but +it does not require this instance to expose anything on the public networks. Actually, an SSH tunnel is created +between the Google internal IAP hosts and the target host & port, going through the bastion VM. ### Kubernetes port forwarding -This feature is still alpha (it was introduced in v 1.14.0). So, don't hesitate to +This feature is still alpha (it was introduced in v 2.0.0). So, don't hesitate to report any good or bad experience using it. Suggestions are welcome too. In order to access a server running in a Kubernetes pod, you can use the 'kubectl' gateway. This gateway -uses K8S port forwarding to open a bidirectionnal tunnel to the target server. Of course it requires the -target server to expose at least one inbound port on a pod. +uses Kubernetes 'port forwarding' mechanism to create a bidirectionnal tunnel to the target server. +Of course, it requires the +target object to expose at least one port. This gateway is activated by setting the 'type' variable to 'kubectl'. @@ -160,17 +159,14 @@ The 'gateway_host' input variable has the same syntax as the 'kubectl port-forwa 'deployment/xxx', 'service/xxx'... Targeting a K8S service, if it exists, should be preferred to benefit from the K8S load-balancing features. -The 'kubectl_context' and 'kubectl_namespace' input variables should be used to determine the target +The 'kubectl_context' and 'kubectl_namespace' input variables should be set to determine the target context and namespace. If it is not set, the current context and namespace (as set in your kube config file) will be used, which is probably -not what you want. You can also fill the 'kubectl_options' variable with additional options to pass -to the 'kubectl' command. - -The 'kubectl_cmd' variable can be set if you cannot use the default command ('kubectl'). +not what you want. You can also enrich the 'kubectl_cmd' variable with additional options. ### External -This feature is still alpha (it was introduced in v 1.14.0). So, don't hesitate to +This feature is still alpha (it was introduced in v 2.0.0). So, don't hesitate to report any good or bad experience using it. Suggestions are welcome too. If the mechanism you want to use is not supported yet, you can provide the code for it. @@ -186,6 +182,25 @@ process managing the tunnel. If other users may be interested, feel free to move it into the 'gateways' subdirectory, add the required documentation in the README.md file, and submit a pull request. +## Tunnel conditional creation + +When the 'create' input variable is false or the 'gateway_host' +input variable is an empty string, no tunnel +is created and the target host and port are returned as outputs, causing a direct connection +to the target. + +This makes the module usable in a scenario where you cannot determine in advance whether +the connection will require a tunnel or not. + +## Environment + +The SSH client process inherits your environment, including a +possible SSH agent configuration to retrieve your private key. + +If you want to set and/or replace environment variables before creating the tunnel, +you can provide a non-empty 'env' input variable. This string will be 'eval'uated +before launching the command to create the tunnel. When using an SSM connection, for instance, it can be used to set the 'AWS_PROFILE' variable. + ## Requirements ### Posix shell @@ -198,20 +213,53 @@ the 'shell_cmd' input variable. On Windows, I think it can run with the cygwin environment but I can't help on this. -### timeout +### 'timeout' utility This module requires the 'timeout' utility to be installed on the host. On MacOS, the 'timeout' utility is not installed by default. You can install it via homebrew with 'brew install coreutils' (source: https://stackoverflow.com/a/70109348). -### nohup +### 'nohup' utility You also need the 'nohup' utility. On MacOS again, it seems 'nohup' may be missing and people have reported using 'gnohup' instead. If you are on MacOS and can provide some logic to check for this and -determine the 'nohup' path to use, you're welcome. +determine the command to use, please tell me. + +### SSH client + +In order to use the 'ssh' or 'ssm' gateways, an SSH client software must be installed on the terraform host. +The command to launch this client can be modified via the 'ssh_cmd' variable. + +### AWS CLI + +The 'ssm' gateway type requires AWS CLI to be installed and configured (the profile whose name is +passed as 'aws_profile' must be defined). + +The EC2 gateway instance must run an SSM agent configured to allow SSM SSH sessions and permissions must +be set accordingly. IAM policies must also allow access from the EC2 gateway to the target host & port. + +### Kubectl + +In order to use the 'kubectl' gateway, you need the 'kubectl' utility to be installed on the terraform host. + +You also need : + +- the 'kubectl_context' you pass to be defined in the kube config file, +- appropriate permissions to access the corresponding cluster, +- the namespace you set as 'kubectl_namespace' to exist +- and, of course, the target resource must exist in this namespace. + +### gcloud CLI + +The 'iap' gateway type requires [gcloud CLI to be installed](https://cloud.google.com/sdk/docs/install) +and configured on the terraform host. + +IAP and the bastion host must also be configured to allow creating an SSH tunnel from +the IAP hosts (35.235.240.0/20 according to the [Google documentation](https://cloud.google.com/iap/docs/using-tcp-forwarding)) +to the target host & port (see [this page](https://www.padok.fr/en/blog/iap-gcp-bastion-apis) for more details). ## Limitations @@ -245,7 +293,7 @@ permissions. module db_tunnel { # You can also retrieve this module from the terraform registry source = "flaupretre/tunnel/ssh" - version = "1.14.0" + version = "2.0.0" target_host = aws_db_instance.mydb.address target_port = aws_db_instance.mydb.port @@ -302,27 +350,28 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [aws\_profile](#input\_aws\_profile) | AWS SSM only - AWS profile | `string` | `""` | no | | [create](#input\_create) | If false, do nothing and return target host | `bool` | `true` | no | -| [env](#input\_env) | String to eval before launching the tunnel | `string` | `""` | no | -| [external\_script](#input\_external\_script) | Path of external script if type is 'external' | `string` | `"undef"` | no | -| [gateway\_host](#input\_gateway\_host) | Gateway (name or IP for SSH, Instance ID for SSM) - empty if no gateway (direct connection) | `any` | `""` | no | +| [env](#input\_env) | An array of name -> value environment variables | `any` | `{}` | no | +| [external\_script](#input\_external\_script) | External only - Path of shell script to run to open the tunnel | `string` | `"undef"` | no | +| [gateway\_host](#input\_gateway\_host) | Gateway (syntax and meaning depend on gateway type - empty if no gateway (direct connection) | `any` | `""` | no | | [gateway\_port](#input\_gateway\_port) | Gateway port | `number` | `22` | no | -| [gateway\_user](#input\_gateway\_user) | User to use on SSH gateway (default = empty string = current username) | `any` | `""` | no | -| [kubectl\_cmd](#input\_kubectl\_cmd) | Alternate command for 'kubectl' | `string` | `"kubectl"` | no | +| [gateway\_user](#input\_gateway\_user) | User to use on gateway (default for SSH : current user) | `any` | `""` | no | +| [kubectl\_cmd](#input\_kubectl\_cmd) | Alternate command for 'kubectl' (including options) | `string` | `"kubectl"` | no | | [kubectl\_context](#input\_kubectl\_context) | Kubectl target context | `string` | `""` | no | | [kubectl\_namespace](#input\_kubectl\_namespace) | Kubectl target namespace | `string` | `""` | no | -| [kubectl\_options](#input\_kubectl\_options) | Kubectl additional options | `string` | `""` | no | -| [local\_host](#input\_local\_host) | Local host name or IP. Set only if you cannot use the '127.0.0.1' default value | `string` | `"127.0.0.1"` | no | +| [local\_host](#input\_local\_host) | Local host name or IP. Set only if you cannot use '127.0.0.1' | `string` | `"127.0.0.1"` | no | +| [parent\_wait\_sleep](#input\_parent\_wait\_sleep) | extra time to wait in the parent process for the child to create the tunnel | `string` | `"3"` | no | | [putin\_khuylo](#input\_putin\_khuylo) | Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo! | `bool` | `true` | no | -| [shell\_cmd](#input\_shell\_cmd) | Command to run a shell | `string` | `"bash"` | no | -| [ssh\_cmd](#input\_ssh\_cmd) | Shell command to use to start ssh client | `string` | `"ssh -o StrictHostKeyChecking=no"` | no | -| [ssh\_parent\_wait\_sleep](#input\_ssh\_parent\_wait\_sleep) | extra time to wait in the tunnel parent process for the child ssh tunnel startup | `string` | `"3"` | no | -| [ssh\_tunnel\_check\_sleep](#input\_ssh\_tunnel\_check\_sleep) | extra time to wait for ssh tunnel to connect | `string` | `"0"` | no | -| [ssm\_document\_name](#input\_ssm\_document\_name) | For SSM only - SSM Document Name | `string` | `"AWS-StartSSHSession"` | no | -| [target\_host](#input\_target\_host) | The target host. Name will be resolved by gateway | `string` | n/a | yes | +| [shell\_cmd](#input\_shell\_cmd) | Alternate command to launch a Posix shell | `string` | `"bash"` | no | +| [ssh\_cmd](#input\_ssh\_cmd) | Alternate command to launch the SSH client (including options) | `string` | `"ssh -o StrictHostKeyChecking=no"` | no | +| [ssm\_document\_name](#input\_ssm\_document\_name) | AWS SSM only - SSM Document Name | `string` | `"AWS-StartSSHSession"` | no | +| [ssm\_options](#input\_ssm\_options) | AWS SSM only - Options to add to the 'aws ssm start-session' command line | `string` | `""` | no | +| [target\_host](#input\_target\_host) | Target host | `string` | n/a | yes | | [target\_port](#input\_target\_port) | Target port number | `number` | n/a | yes | -| [timeout](#input\_timeout) | Timeout value ensures tunnel won't remain open forever | `string` | `"30m"` | no | -| [type](#input\_type) | Tunnel type (['ssh'], 'ssm', or 'external') | `string` | `"ssh"` | no | +| [timeout](#input\_timeout) | Timeout value ensures tunnel won't remain open forever - do not change | `string` | `"30m"` | no | +| [tunnel\_check\_sleep](#input\_tunnel\_check\_sleep) | extra time to wait for the tunnel to become available | `string` | `"0"` | no | +| [type](#input\_type) | Gateway type : 'ssh' (default), 'ssm', 'kubectl', or 'external' | `string` | `"ssh"` | no | ## Outputs diff --git a/gateways/iap.sh b/gateways/iap.sh new file mode 100644 index 0000000..f4c0f8a --- /dev/null +++ b/gateways/iap.sh @@ -0,0 +1,22 @@ +# Google IAP Gateway +# +#------WARNING--WARNING--WARNING--WARNING--WARNING--WARNING--WARNING-- +# +# The code below is purely EXPERIMENTAL. I wrote it +# from information I got on the Internet and I could not +# test it as I don't have access to a GCP platform. +# +# If you have access to a GCP platform and can test this, your return will be +# warmly appreciated. +#---------------------------------------------------------------------------- + +gw="$GATEWAY_HOST" +[ "X$GATEWAY_USER" = X ] || gw="$GATEWAY_USER@$GATEWAY_HOST" + +$GCLOUD_CMD compute ssh \ + --tunnel-through-iap \ + --ssh-key-expire-after $TIMEOUT \ + --ssh-flag="-N -L $LOCAL_HOST:$LOCAL_PORT:$TARGET_HOST:$TARGET_PORT -p $GATEWAY_PORT" \ + $gw & + +CPID=$! diff --git a/gateways/kubectl.sh b/gateways/kubectl.sh index 79b1dff..52c16d6 100644 --- a/gateways/kubectl.sh +++ b/gateways/kubectl.sh @@ -5,13 +5,12 @@ # KUBECTL_CMD: Alternate command to run kubectl (default: 'kubectl') # KUBECTL_CONTEXT: Context name (as defined in kubectl config file, default: current context) # KUBECTL_NAMESPACE: Kubernetes namespace (default: current namespace) -# KUBECTL_OPTIONS: Other options (default: empty) #---------------------------------------------------------------------------- -opts="$KUBECTL_OPTIONS" -[ -n "$KUBECTL_CONTEXT" ] && opts="$opts --context=$KUBECTL_CONTEXT" -[ -n "$KUBECTL_NAMESPACE" ] && opts="$opts --namespace=$KUBECTL_NAMESPACE" +cmd="$KUBECTL_CMD" +[ -n "$KUBECTL_CONTEXT" ] && cmd="$cmd --context=$KUBECTL_CONTEXT" +[ -n "$KUBECTL_NAMESPACE" ] && cmd="$cmd --namespace=$KUBECTL_NAMESPACE" -$KUBECTL_CMD port-forward $opts "$GATEWAY_HOST" $LOCAL_PORT:$TARGET_PORT & +$cmd port-forward "$GATEWAY_HOST" $LOCAL_PORT:$TARGET_PORT & CPID=$! diff --git a/gateways/ssm.sh b/gateways/ssm.sh index fcae4bc..955730f 100644 --- a/gateways/ssm.sh +++ b/gateways/ssm.sh @@ -11,7 +11,7 @@ gw="$GATEWAY_HOST" [ "X$GATEWAY_USER" = X ] || gw="$GATEWAY_USER@$GATEWAY_HOST" $SSH_CMD \ - -o ProxyCommand "aws ssm start-session --target %h --document-name $SSM_DOCUMENT_NAME --parameters 'portNumber=%p'" \ + -o ProxyCommand "aws ssm start-session $SSM_OPTIONS --target %h --document-name $SSM_DOCUMENT_NAME --parameters 'portNumber=%p'" \ -N \ -L $LOCAL_HOST:$LOCAL_PORT:$TARGET_HOST:$TARGET_PORT \ -p $GATEWAY_PORT \ diff --git a/main.tf b/main.tf index 0898f32..e79a26c 100644 --- a/main.tf +++ b/main.tf @@ -11,26 +11,27 @@ data external ssh_tunnel { "${path.module}/tunnel.sh" ] query = { - type = var.type - timeout = var.timeout - ssh_cmd = var.ssh_cmd - local_host = var.local_host - local_port = data.external.free_port.result.port - target_host = var.target_host - target_port = var.target_port + aws_profile = var.aws_profile + create = ((var.create && var.putin_khuylo) ? "y" : "") + env = join("\n", [ for n,v in var.env : "export ${n}=\"${replace("\"", "\\\"", v)}\""]) + external_script = var.external_script gateway_host = var.gateway_host gateway_port = var.gateway_port gateway_user = var.gateway_user - shell_cmd = var.shell_cmd - ssh_tunnel_check_sleep = var.ssh_tunnel_check_sleep - ssh_parent_wait_sleep = var.ssh_parent_wait_sleep - create = ((var.create && var.putin_khuylo) ? "y" : "") - env = var.env - ssm_document_name = var.ssm_document_name - external_script = var.external_script kubectl_cmd = var.kubectl_cmd kubectl_context = var.kubectl_context kubectl_namespace = var.kubectl_namespace - kubectl_options = var.kubectl_options + local_host = var.local_host + local_port = data.external.free_port.result.port + parent_wait_sleep = var.parent_wait_sleep + shell_cmd = var.shell_cmd + ssh_cmd = var.ssh_cmd + ssm_document_name = var.ssm_document_name + ssm_options = var.ssm_options + target_host = var.target_host + target_port = var.target_port + timeout = var.timeout + tunnel_check_sleep = var.tunnel_check_sleep + type = var.type } } diff --git a/tunnel.sh b/tunnel.sh index 137b8d7..ec92825 100755 --- a/tunnel.sh +++ b/tunnel.sh @@ -6,7 +6,7 @@ ret=0 if [ -z "$MPID" ] ; then if [ -n "$TUNNEL_DEBUG" ] ; then - exec 2>/tmp/t1 + exec 2>/tmp/t1.$$ set -x env >&2 fi @@ -16,27 +16,31 @@ if [ -z "$MPID" ] ; then query="`dd 2>/dev/null`" [ -n "$TUNNEL_DEBUG" ] && echo "query: <$query>" >&2 - export TYPE="`echo $query | sed -e 's/^.*\"type\": *\"//' -e 's/\".*$//g'`" - export TIMEOUT="`echo $query | sed -e 's/^.*\"timeout\": *\"//' -e 's/\".*$//g'`" - export SSH_CMD="`echo $query | sed -e 's/^.*\"ssh_cmd\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" - export LOCAL_HOST="`echo $query | sed -e 's/^.*\"local_host\": *\"//' -e 's/\".*$//g'`" - export LOCAL_PORT="`echo $query | sed -e 's/^.*\"local_port\": *\"//' -e 's/\".*$//g'`" - export TARGET_HOST="`echo $query | sed -e 's/^.*\"target_host\": *\"//' -e 's/\".*$//g'`" - export TARGET_PORT="`echo $query | sed -e 's/^.*\"target_port\": *\"//' -e 's/\".*$//g'`" - export GATEWAY_HOST="`echo $query | sed -e 's/^.*\"gateway_host\": *\"//' -e 's/\".*$//g'`" - export GATEWAY_PORT="`echo $query | sed -e 's/^.*\"gateway_port\": *\"//' -e 's/\".*$//g'`" - export GATEWAY_USER="`echo $query | sed -e 's/^.*\"gateway_user\": *\"//' -e 's/\".*$//g'`" - export SHELL_CMD="`echo $query | sed -e 's/^.*\"shell_cmd\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" - export SSH_TUNNEL_CHECK_SLEEP="`echo $query | sed -e 's/^.*\"ssh_tunnel_check_sleep\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" - export SSH_PARENT_WAIT_SLEEP="`echo $query | sed -e 's/^.*\"ssh_parent_wait_sleep\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" export CREATE="`echo $query | sed -e 's/^.*\"create\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" export ENV="`echo $query | sed -e 's/^.*\"env\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" - export SSM_DOCUMENT_NAME="`echo $query | sed -e 's/^.*\"ssm_document_name\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" export EXTERNAL_SCRIPT="`echo $query | sed -e 's/^.*\"external_script\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" + export GATEWAY_HOST="`echo $query | sed -e 's/^.*\"gateway_host\": *\"//' -e 's/\".*$//g'`" + export GATEWAY_PORT="`echo $query | sed -e 's/^.*\"gateway_port\": *\"//' -e 's/\".*$//g'`" + export GATEWAY_USER="`echo $query | sed -e 's/^.*\"gateway_user\": *\"//' -e 's/\".*$//g'`" export KUBECTL_CMD="`echo $query | sed -e 's/^.*\"kubectl_cmd\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" export KUBECTL_CONTEXT="`echo $query | sed -e 's/^.*\"kubectl_context\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" export KUBECTL_NAMESPACE="`echo $query | sed -e 's/^.*\"kubectl_namespace\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" - export KUBECTL_OPTIONS="`echo $query | sed -e 's/^.*\"kubectl_options\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" + export LOCAL_HOST="`echo $query | sed -e 's/^.*\"local_host\": *\"//' -e 's/\".*$//g'`" + export LOCAL_PORT="`echo $query | sed -e 's/^.*\"local_port\": *\"//' -e 's/\".*$//g'`" + export PARENT_WAIT_SLEEP="`echo $query | sed -e 's/^.*\"parent_wait_sleep\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" + export SHELL_CMD="`echo $query | sed -e 's/^.*\"shell_cmd\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" + export SSH_CMD="`echo $query | sed -e 's/^.*\"ssh_cmd\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" + export SSM_DOCUMENT_NAME="`echo $query | sed -e 's/^.*\"ssm_document_name\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" + export SSM_OPTIONS="`echo $query | sed -e 's/^.*\"ssm_options\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" + export TARGET_HOST="`echo $query | sed -e 's/^.*\"target_host\": *\"//' -e 's/\".*$//g'`" + export TARGET_PORT="`echo $query | sed -e 's/^.*\"target_port\": *\"//' -e 's/\".*$//g'`" + export TIMEOUT="`echo $query | sed -e 's/^.*\"timeout\": *\"//' -e 's/\".*$//g'`" + export TUNNEL_CHECK_SLEEP="`echo $query | sed -e 's/^.*\"tunnel_check_sleep\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" + export TYPE="`echo $query | sed -e 's/^.*\"type\": *\"//' -e 's/\".*$//g'`" + + # Set AWS_PROFILE only if var is not empty + profile="`echo $query | sed -e 's/^.*\"aws_profile\": *\"//' -e 's/\",.*$//g' -e 's/\\\"/\"/g'`" + [ -n "$profile" ] && export AWS_PROFILE="$profile" if [ "X$CREATE" = X -o "X$GATEWAY_HOST" = X ] ; then # No tunnel - connect directly to target host @@ -57,7 +61,7 @@ if [ -z "$MPID" ] ; then nohup timeout $TIMEOUT $SHELL_CMD "$ABSPATH/tunnel.sh" $p <&- >&- 2>$clog & CPID=$! # A little time for the SSH tunnel process to start or fail - sleep $SSH_PARENT_WAIT_SLEEP + sleep $PARENT_WAIT_SLEEP # If the child process does not exist anymore after this delay, report failure if ! ps -p $CPID >/dev/null 2>&1 ; then echo "Child process ($CPID) failure - Aborting" >&2 @@ -71,13 +75,13 @@ if [ -z "$MPID" ] ; then else #------ Child if [ -n "$TUNNEL_DEBUG" ] ; then - exec 2>/tmp/t2 + exec 2>/tmp/t2.$$ set -x env >&2 fi CPID="" - TMPFILE="" + TODELETE="" script="$ABSPATH/gateways/$TYPE.sh" if [ ! -f "$script" ]; then @@ -85,20 +89,18 @@ else fi if [ -n "$ENV" ]; then - set -a eval $ENV - set +a fi # Script must set $CPID source "$script" - sleep $SSH_TUNNEL_CHECK_SLEEP + sleep $TUNNEL_CHECK_SLEEP while true ; do if ! ps -p $CPID >/dev/null 2>&1 ; then echo "SSH process ($CPID) failure - Aborting" >&2 - [ -n "$TMPFILE" ] && /bin/rm -rf $TMPFILE + [ -n "$TODELETE" ] && /bin/rm -rf $TODELETE exit 1 fi ps -p $MPID >/dev/null 2>&1 || break @@ -106,7 +108,7 @@ else done kill $CPID - [ -n "$TMPFILE" ] && /bin/rm -rf $TMPFILE + [ -n "$TODELETE" ] && /bin/rm -rf $TODELETE fi exit $ret diff --git a/variables.tf b/variables.tf index 8281196..b78b8fd 100755 --- a/variables.tf +++ b/variables.tf @@ -1,124 +1,131 @@ +variable "putin_khuylo" { + description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!" + type = bool + default = true +} + +variable "aws_profile" { + type = string + description = "AWS SSM only - AWS profile" + default = "" +} + variable "create" { type = bool description = "If false, do nothing and return target host" default = true } -variable "type" { - type = string - description = "Tunnel type (['ssh'], 'ssm', or 'external')" - default = "ssh" -} - -variable "shell_cmd" { - type = string - description = "Command to run a shell" - default = "bash" +variable "env" { + type = any + description = "An array of name -> value environment variables" + default = {} } -variable "ssh_cmd" { +variable "external_script" { type = string - description = "Shell command to use to start ssh client" - default = "ssh -o StrictHostKeyChecking=no" + description = "External only - Path of shell script to run to open the tunnel" + default = "undef" } -variable "env" { - type = string - description = "String to eval before launching the tunnel" +variable "gateway_host" { + type = any + description = "Gateway (syntax and meaning depend on gateway type - empty if no gateway (direct connection)" default = "" } -variable "local_host" { - type = string - description = "Local host name or IP. Set only if you cannot use the '127.0.0.1' default value" - default="127.0.0.1" +variable "gateway_port" { + type = number + description = "Gateway port" + default = 22 } -variable "target_host" { - type = string - description = "The target host. Name will be resolved by gateway" +variable "gateway_user" { + type = any + description = "User to use on gateway (default for SSH : current user)" + default = "" } -variable "target_port" { - type = number - description = "Target port number" +variable "kubectl_cmd" { + type = string + description = "Alternate command for 'kubectl' (including options)" + default = "kubectl" } -variable "gateway_host" { - type = any +variable "kubectl_context" { + type = string + description = "Kubectl target context" default = "" - description = "Gateway (name or IP for SSH, Instance ID for SSM) - empty if no gateway (direct connection)" } -variable "gateway_user" { - type = any - description = "User to use on SSH gateway (default = empty string = current username)" +variable "kubectl_namespace" { + type = string + description = "Kubectl target namespace" default = "" } -variable "gateway_port" { - type = number - description = "Gateway port" - default = 22 +variable "local_host" { + type = string + description = "Local host name or IP. Set only if you cannot use '127.0.0.1'" + default="127.0.0.1" } -variable "timeout" { +variable "parent_wait_sleep" { type = string - description = "Timeout value ensures tunnel won't remain open forever" - default = "30m" + description = "extra time to wait in the parent process for the child to create the tunnel" + default = "3" } -variable "ssh_tunnel_check_sleep" { +variable "shell_cmd" { type = string - description = "extra time to wait for ssh tunnel to connect" - default = "0" + description = "Alternate command to launch a Posix shell" + default = "bash" } -variable "ssh_parent_wait_sleep" { +variable "ssh_cmd" { type = string - description = "extra time to wait in the tunnel parent process for the child ssh tunnel startup" - default = "3" + description = "Alternate command to launch the SSH client (including options)" + default = "ssh -o StrictHostKeyChecking=no" } variable "ssm_document_name" { type = string - description = "For SSM only - SSM Document Name" + description = "AWS SSM only - SSM Document Name" default = "AWS-StartSSHSession" } -variable "external_script" { +variable "ssm_options" { type = string - description = "Path of external script if type is 'external'" - default = "undef" + description = "AWS SSM only - Options to add to the 'aws ssm start-session' command line" + default = "" } -variable "kubectl_cmd" { +variable "target_host" { type = string - description = "Alternate command for 'kubectl'" - default = "kubectl" + description = "Target host" } -variable "kubectl_context" { - type = string - description = "Kubectl target context" - default = "" +variable "target_port" { + type = number + description = "Target port number" } -variable "kubectl_namespace" { +variable "timeout" { type = string - description = "Kubectl target namespace" - default = "" + description = "Timeout value ensures tunnel won't remain open forever - do not change" + default = "30m" } -variable "kubectl_options" { +variable "tunnel_check_sleep" { type = string - description = "Kubectl additional options" - default = "" + description = "extra time to wait for the tunnel to become available" + default = "0" } -variable "putin_khuylo" { - description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!" - type = bool - default = true +variable "type" { + type = string + description = "Gateway type : 'ssh' (default), 'ssm', 'kubectl', or 'external'" + default = "ssh" } +