Last active
September 30, 2019 12:09
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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