Skip to content

Instantly share code, notes, and snippets.

@goncalomb
Last active February 5, 2019 23:07
Show Gist options
  • Save goncalomb/25f5d0e097a8c981bd24de32d22859a9 to your computer and use it in GitHub Desktop.
Save goncalomb/25f5d0e097a8c981bd24de32d22859a9 to your computer and use it in GitHub Desktop.
Kubernetes context protector and utilities
# Kubernetes context protector and utilities
# Copyright (c) 2019 Gonçalo Baltazar <[email protected]>
# MIT License
# protects from accidental operations to the incorrect kubectl context
# alerts the user when calling `kubectl` and `helm` on a production context
# assumes that `kubectl` and `helm` are installed and available on PATH
# also adds `ku` command as a `kubectl` alias with some extra features
# ku all - lists all kubernetes objects on all namespaces
# ku ctx - menu for changing context (requires `dialog`)
# ku pf - menu for port-forwarding (requires `dialog`)
# ku lf - menu for log following (requires `dialog`)
# to install just source from .bashrc (. "$HOME/k8s-protect") or copy the code
# config, timeout in seconds to confirm dangerous context again
export K8S_PROTECT_TIMEOUT=30
# config end
KUBECTL_PATH=`which kubectl`
HELM_PATH=`which helm`
TMP_DIR=$(dirname "$(mktemp -u)")
TMP_K8S_DIR="$TMP_DIR/k8s-protect"
TMP_K8S_BIN_DIR="$TMP_K8S_DIR/bin"
TMP_CONTEXT_FILE="$TMP_K8S_DIR/context"
protect_command_proxy() {
echo -en "#!/bin/bash\n\n\"$TMP_K8S_DIR/protector\" \"$2\" \"\$@\"\n" > "$TMP_K8S_BIN_DIR/$1"
chmod +x "$TMP_K8S_BIN_DIR/$1"
}
if [ ! -d "$TMP_K8S_DIR" ]; then
mkdir -p "$TMP_K8S_BIN_DIR"
# create protector script
cat > "$TMP_K8S_DIR/protector" << EOF
#!/bin/bash
set -e
# if requested, run command without protection
if [[ "\$2" == "--k8s-protect-disable" ]]; then
"\$1" "\${@:3}"
exit
fi
CONTEXT=\$("$KUBECTL_PATH" config current-context)
[[ -f "$TMP_CONTEXT_FILE" ]] && CONTEXT_LAST=\$(cat "$TMP_CONTEXT_FILE") || CONTEXT_LAST=
confirm() {
read -r -p "\$1 (y/n)? " YESNO
if [[ "\$YESNO" =~ ^[yY] ]]; then true; else false; fi
}
if [[ -z "\$CONTEXT_LAST" || "\$CONTEXT" != "\$CONTEXT_LAST" || \$(stat -c %Y "$TMP_CONTEXT_FILE") < \$((\$(date +%s) - \$K8S_PROTECT_TIMEOUT)) ]]; then
>&2 echo
>&2 echo "k8s-protect: context = \$CONTEXT"
if [[ "\$CONTEXT" == *prod* ]]; then
confirm "k8s-protect: DANGEROUS CONTEXT, continue" || exit 1
>&2 echo "k8s-protect: will confirm again after \$K8S_PROTECT_TIMEOUT seconds"
elif [[ "\$CONTEXT" != "\$CONTEXT_LAST" ]]; then
confirm "k8s-protect: confirm context, continue" || exit 1
>&2 echo "k8s-protect: won't confirm this context again"
fi
>&2 echo
fi
echo "\$CONTEXT" > "$TMP_CONTEXT_FILE"
"\$@"
EOF
chmod +x "$TMP_K8S_DIR/protector"
# create program proxies
[[ -n "$KUBECTL_PATH" ]] && protect_command_proxy kubectl "$KUBECTL_PATH"
[[ -n "$HELM_PATH" ]] && protect_command_proxy helm "$HELM_PATH"
echo "k8s-protect: enabling context protection"
fi
# add to path if not already there
[[ ":$PATH:" != *":$TMP_K8S_BIN_DIR:"* ]] && PATH="$TMP_K8S_BIN_DIR:$PATH"
# check protection
KUBECTL_PATH=`which kubectl`
HELM_PATH=`which helm`
[[ -n "$KUBECTL_PATH" && "$KUBECTL_PATH" != "$TMP_K8S_BIN_DIR/kubectl" ]] || \
[[ -n "$HELM_PATH" && "$HELM_PATH" != "$TMP_K8S_BIN_DIR/helm" ]] && \
echo "k8s-protect: something is not right, context protection may not be working" || true
# ku command
ku() {
if [[ "$1" == "all" ]]; then
kubectl get all --all-namespaces
elif [[ "$1" == "ctx" ]]; then
if command -v dialog > /dev/null; then
CTXS=$(kubectl --k8s-protect-disable config get-contexts -o name)
CTX=$(kubectl --k8s-protect-disable config current-context)
if [ -n "$CTXS" ]; then
tput smcup
CTX=$(echo "$CTXS" | sed 's/$/\n""/' | xargs dialog --keep-tite --default-item "$CTX" --menu "Select context to use..." 0 0 0 3>&1 1>&2 2>&3)
tput rmcup
[ -n "$CTX" ] && kubectl --k8s-protect-disable config use-context "$CTX"
fi
else
kubectl --k8s-protect-disable config get-contexts
fi
elif [[ "$1" == "pf" ]]; then
PORT_LIST=$(kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace} {.metadata.name}{"\n"}{range .spec.containers[*].ports[?(.protocol=="TCP")]} {.containerPort}{"\n"}{end}{end}')
if [[ -n "$PORT_LIST" ]]; then
declare -A PORTS_BY_POD
CMD=
POD=
PORTS=
while IFS='' read -r LINE; do
if [[ "${LINE:0:1}" != " " ]]; then
[[ -n "$PORTS" ]] && CMD="$CMD \"$POD\" \"$PORTS\"" && PORTS_BY_POD[$POD]=$PORTS && PORTS=
POD="$LINE"
elif [[ -z "$PORTS" ]]; then
PORTS=":${LINE:1}"
else
PORTS="$PORTS :${LINE:1}"
fi
done < <(echo "$PORT_LIST")
[[ -n "$PORTS" ]] && CMD="$CMD \"$POD\" \"$PORTS\"" && PORTS_BY_POD[$POD]=$PORTS && PORTS=
tput smcup
POD=$(eval "dialog --keep-tite --menu \"Select pod for port forwarding...\" 0 0 0 $CMD 3>&1 1>&2 2>&3")
tput rmcup
if [[ -n "$POD" ]]; then
echo "$POD"; echo
eval "kubectl port-forward --address=127.0.0.1 -n $POD ${PORTS_BY_POD[$POD]}" | sed -e 's/^Forwarding from \(.*\) ->.*$/\0\n http:\/\/\1/'
fi
fi
elif [[ "$1" == "lf" ]]; then
PODS=$(kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace} {.metadata.name}{"\n"}{end}')
if [[ -n "$PODS" ]]; then
tput smcup
POD=$(echo "$PODS" | sed 's/.*/"\0"\n""/' | xargs dialog --keep-tite --menu "Select pod for log following..." 0 0 0 3>&1 1>&2 2>&3)
tput rmcup
if [[ -n "$POD" ]]; then
echo "$POD"; echo
eval "kubectl logs --tail=200 -f --all-containers=true -n $POD"
fi
fi
else kubectl "$@"
fi
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment