Last active
February 5, 2019 23:07
-
-
Save goncalomb/25f5d0e097a8c981bd24de32d22859a9 to your computer and use it in GitHub Desktop.
Kubernetes context protector and utilities
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
# 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