#!/bin/sh set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT_DIR="$(realpath "$SCRIPT_DIR/..")" LIB_DIR="$ROOT_DIR/scripts" CLUSTER_ENV_WORK="${CLUSTER_ENV_WORK:-$ROOT_DIR/configs/cluster.env.work}" KUBECTL="${KUBECTL:-kubectl}" TTL_HOURS="${TTL_HOURS:-24}" WAIT_SECONDS="${WAIT_SECONDS:-30}" need() { command -v "$1" >/dev/null 2>&1 || { echo "missing required command: $1" >&2 exit 1 } } rfc3339_after_hours() { hours="$1" # GNU date if date -u -d "+${hours} hours" '+%Y-%m-%dT%H:%M:%SZ' >/dev/null 2>&1; then date -u -d "+${hours} hours" '+%Y-%m-%dT%H:%M:%SZ' return fi # BSD/macOS date if date -u -v+"${hours}"H '+%Y-%m-%dT%H:%M:%SZ' >/dev/null 2>&1; then date -u -v+"${hours}"H '+%Y-%m-%dT%H:%M:%SZ' return fi echo "cannot compute expiration time with this date(1). Set EXPIRATION manually." >&2 exit 1 } decode_base64_to_file() { input="$1" output="$2" if printf '%s' "$input" | base64 -d >"$output" 2>/dev/null; then return fi if printf '%s' "$input" | base64 -D >"$output" 2>/dev/null; then return fi if printf '%s' "$input" | openssl base64 -d -A >"$output" 2>/dev/null; then return fi echo "failed to decode certificate-authority-data" >&2 exit 1 } need "$KUBECTL" need openssl need awk need sed TOKEN_ID="${TOKEN_ID:-$(openssl rand -hex 3)}" TOKEN_SECRET="${TOKEN_SECRET:-$(openssl rand -hex 8)}" TOKEN="${TOKEN_ID}.${TOKEN_SECRET}" SECRET_NAME="bootstrap-token-${TOKEN_ID}" if [ "${TTL_HOURS}" = "0" ]; then EXPIRATION="" else EXPIRATION="${EXPIRATION:-$(rfc3339_after_hours "$TTL_HOURS")}" fi echo "Creating bootstrap token Secret: ${SECRET_NAME}" >&2 { cat <&2 echo "token was created, but cannot print a safe kubeadm join command" >&2 echo "token: ${TOKEN}" exit 0 fi decode_base64_to_file "$CA_DATA" "$CA_FILE" CA_HASH="$( openssl x509 -in "$CA_FILE" -pubkey -noout | openssl pkey -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | awk '{print $2}' )" SERVER="$("$KUBECTL" config view --raw --minify -o jsonpath='{.clusters[0].cluster.server}')" JOIN_ENDPOINT="$(printf '%s\n' "$SERVER" | sed -E 's#^https?://##')" echo "Waiting for cluster-info signature for token ${TOKEN_ID}..." >&2 i=0 signed="false" while [ "$i" -lt "$WAIT_SECONDS" ]; do template="{{ index .data \"jws-kubeconfig-${TOKEN_ID}\" }}" sig="$("$KUBECTL" -n kube-public get configmap cluster-info -o "go-template=${template}" 2>/dev/null || true)" if [ -n "$sig" ]; then signed="true" break fi i=$((i + 1)) sleep 1 done echo echo "Token:" echo " ${TOKEN}" if [ -n "$EXPIRATION" ]; then echo echo "Expires:" echo " ${EXPIRATION}" fi echo echo "Join command:" echo " kubeadm join ${JOIN_ENDPOINT} --token ${TOKEN} --discovery-token-ca-cert-hash sha256:${CA_HASH}" TMP_ENV="$(mktemp)" trap 'rm -f "$TMP_ENV"; rm -rf "$TMPDIR"' EXIT INT TERM cat >"$TMP_ENV" <&2 exit 1 fi "$LIB_DIR/merge-env.sh" "$TMP_ENV" "$CLUSTER_ENV_WORK" echo echo "Merged into:" echo " $CLUSTER_ENV_WORK" echo echo "Try" cat <&2 echo "warning: cluster-info was not signed within ${WAIT_SECONDS}s." >&2 echo "If kubeadm join fails discovery, check that kube-controller-manager enables bootstrapsigner." >&2 fi