Skip to content

Instantly share code, notes, and snippets.

@anapsix
Last active September 30, 2019 12:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anapsix/5a4f23899298bd592e094de380978c89 to your computer and use it in GitHub Desktop.
Save anapsix/5a4f23899298bd592e094de380978c89 to your computer and use it in GitHub Desktop.
Wrapper for kubectl to automatically establish SSH connection to jumphost, though which to proxy requests to K8s API
#!/usr/bin/env bash
#
# DEPRECATED
# use k8s-vault instead
# https://gist.github.com/anapsix/b5af204162c866431cd5640aef769610
#
#
# Wrapper for kubectl to establish SSH connection to jumphost,
# though which to proxy requests to K8s API
#
# Copyright (C) 2019 Anastas Dancha (aka @anapsix)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# Use it via alias
# alias kubectl='~/bin/kubectl_with_ssh_jumphost'
# Example of config file
: <<'EXAMPLE_CONFIG_YAML'
k8s_api_timeout: 10
ssh_forwarding_port:
random: false
static_port: 32845
clusters:
prod:
enabled: true
ssh_jump_host: jumphost.prod.example.com
qa:
enabled: true
ssh_jump_host: jumphost.qa.example.com
dev:
enabled: false
ssh_jump_host: jumphost.dev.example.com
EXAMPLE_CONFIG_YAML
# Dependencies
: <<'DEPENDENCIES'
- jq
- yq
- bash
- ggrep
- kubectl
- openssh-client
DEPENDENCIES
set -e
: ${KUBECONFIG:=${HOME}/.kube/config}
DEBUG=0
info() {
echo >&2 -e "\e[92mINFO:\e[0m $@"
}
debug(){
if [[ ${DEBUG:-0} -eq 1 ]]; then
echo >&2 -e "\e[95mDEBUG:\e[0m $@"
fi
}
error(){
local msg="$1"
local exit_code="${2:-1}"
echo >&2 -e "\e[91mERROR:\e[0m $1"
if [[ "${exit_code}" != "-" ]]; then
exit ${exit_code}
fi
}
[[ "$(uname)" == "Darwin" ]] && grep="ggrep" || grep="grep"
CONFIG_DIR="${HOME}/.kube/sshproxy"
CONFIG_FILE="${CONFIG_DIR}/config.yaml"
[[ -d ${CONFIG_DIR} ]] || mkdir ${CONFIG_DIR}
debug "CONFIG_FILE: ${CONFIG_FILE}"
read_config_value() {
yq r "${CONFIG_FILE}" $1
}
[[ -r "${CONFIG_FILE}" ]] || \
error "Unable to read config file at \"${CONFIG_FILE}\", exiting.."
ALL_OPTS="$@"
passthrough() {
exec kubectl ${ALL_OPTS}
}
## Get CLI arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help|--usage)
usage
exit 0
;;
--debug)
DEBUG=1
shift 1
;;
--context|--context=*)
if [[ "${1:9:1}" == "=" ]]; then
KUBECTL_CONTEXT=${1##*=}
shift 1
else
KUBECTL_CONTEXT="$2"
shift 2
fi
quick_draw="$(read_config_value clusters.${KUBECTL_CONTEXT}.enabled)"
if [[ ${quick_draw} != "true" ]]; then
unset quick_draw
passthrough
fi
;;
--kubeconfig|--kubeconfig=*)
if [[ "${1:12:1}" == "=" ]]; then
KUBECONFIG=${1##*=}
shift 1
else
KUBECONFIG="$2"
shift 2
fi
debug "KUBECONFIG: ${KUBECONFIG}"
exit 0
;;
--)
shift 1
break
;;
-*)
error "unrecognized option '$1'" -
info "to see usage, use $0 --help"
info "or make sure to \"--\" to indicate all wrapper options are passed"
exit 1
;;
*)
break
;;
esac
done
read_kubectl_config(){
yq r "${KUBECONFIG}" $1
}
read_kubectl_config_json(){
yq r -j "${KUBECONFIG}" $1
}
## Getting config values
SSH_RANDOM_PORT_ENABLED="$(read_config_value ssh_forwarding_port.random)"
if [[ "${SSH_RANDOM_PORT_ENABLED:-false}" == "true" ]]; then
debug "SSH Random Forwarding Port enabled"
SSH_FORWARDING_PORT=$[$[RANDOM%9000]+30000]
debug "Using random-generated port: ${SSH_FORWARDING_PORT}"
else
debug "SSH Random Forwarding Port disabled"
SSH_FORWARDING_PORT="$(read_config_value ssh_forwarding_port.static)"
debug "Using port from config: ${SSH_FORWARDING_PORT}"
fi
K8S_API_CONNECT_TIMEOUT="$(read_config_value k8s_api_timeout)"
debug "K8s API Connection Timeout: ${K8S_API_CONNECT_TIMEOUT}"
if [[ -z "${KUBECTL_CONTEXT}" ]]; then
KUBECTL_CONTEXT="$(read_kubectl_config current-context)"
fi
debug "K8s Context: ${KUBECTL_CONTEXT}"
KUBECTL_CLUSTER="$(
read_kubectl_config_json contexts | \
jq -r --arg context ${KUBECTL_CONTEXT} '.[] | select(.name==$context) | .context.cluster'
)"
debug "K8s Cluster: ${KUBECTL_CLUSTER}"
KUBECTL_CLUSTER_SERVER_URL="$(
read_kubectl_config_json clusters | \
jq -r --arg cluster ${KUBECTL_CLUSTER} '.[] | select(.name==$cluster) | .cluster.server'
)"
debug "K8s API Server URL: ${KUBECTL_CLUSTER_SERVER_URL}"
KUBECTL_CLUSTER_SERVER_HOST="$(echo "${KUBECTL_CLUSTER_SERVER_URL}" | $grep -Po "(?<=//)[^:]+")"
debug "K8s API Server Host: ${KUBECTL_CLUSTER_SERVER_HOST}"
KUBECTL_CLUSTER_SERVER_PORT="$(echo "${KUBECTL_CLUSTER_SERVER_URL}" | $grep -Po "(?<=:)[0-9]+")"
debug "K8s API Server Port: ${KUBECTL_CLUSTER_SERVER_PORT}"
KUBECTL_OPTS="--server=https://127.0.0.1:${SSH_FORWARDING_PORT} --insecure-skip-tls-verify"
SSH_JUMP_HOST="$(read_config_value clusters.${KUBECTL_CLUSTER}.ssh_jump_host)"
debug "SSH Jumphost: ${SSH_JUMP_HOST}"
SSH_PORT_FORWARD_OPT="-L${SSH_FORWARDING_PORT}:${KUBECTL_CLUSTER_SERVER_HOST}:${KUBECTL_CLUSTER_SERVER_PORT}"
SSH_PID=""
function _exit {
debug 'Shutting down SSH Port-Forward..'
if kill -0 ${SSH_PID:-99999999} 2>/dev/null; then
kill $SSH_PID
fi
}
trap _exit EXIT
start_ssh_session(){
ssh -N ${SSH_PORT_FORWARD_OPT} ${SSH_JUMP_HOST} &
SSH_PID="$!"
}
check_connection() {
local check_start_epoch="$(date +%s)"
local now_epoch
local elapsed
until (echo "" | nc 127.0.0.1 ${SSH_FORWARDING_PORT}); do
now_epoch="$(date +%s)"
elapsed=$((now_epoch-check_start_epoch))
debug "could not tcp connect to K8s API (elapsed: $elapsed).."
if [[ ${elapsed} -gt ${K8S_API_CONNECT_TIMEOUT} ]]; then
error "Could not connect to K8s API within specified timeout (${K8S_API_CONNECT_TIMEOUT}s)"
fi
sleep 0.5
done
}
start_ssh_session
debug >&2 "SSH Proxy PID: $SSH_PID"
check_connection
kubectl --context=${KUBECTL_CONTEXT} ${KUBECTL_OPTS} $@
# EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment