#!/bin/sh # Доступ разработчика к k3s по токену ZITADEL: ставит kubectl и kubelogin, дописывает контекст # production-oidc в ~/.kube/config. Чужие контексты, кластеры и пользователи не трогаются. # curl -fsSL https://docs.iconicompany.com/install/k8s.sh | sh # Переменные: BIN_DIR (куда ставить, по умолчанию ~/.local/bin), KUBELOGIN_VERSION, KUBECTL_VERSION. set -eu # Всё в main(): при оборванной загрузке `curl | sh` не выполнится ничего. main() { KUBELOGIN_VERSION="${KUBELOGIN_VERSION:-v1.36.4}" KUBELOGIN_MIN="1.36.4" # Суммы v1.36.4 зашиты: сумма рядом с релизом не защищает от подмены самого релиза. KUBELOGIN_SHA256_linux_amd64=9e8baeb4905d35a301304af2548324997add512fe94ab20e65993d167212bf85 KUBELOGIN_SHA256_linux_arm64=669c2d3bcc6766351ad5a7b03971792b4e7a55117b335a12d9858d43a525d9dc KUBELOGIN_SHA256_darwin_amd64=7927b10c122b55dd4d9632f75d56752bfdc5f7b6ad3104615115c02f5f55e4f0 KUBELOGIN_SHA256_darwin_arm64=5aa470d059a9672d732439474678ac05a0de5623ad3af54966b8fe1ac7df4254 BIN_DIR="${BIN_DIR:-$HOME/.local/bin}" SERVER="https://kube01.icncd.ru:6443" ISSUER="https://id.iconicompany.com" CLIENT_ID="390970312646262924" # ik8s-cli, публичный клиент (PKCE, без секрета) CLUSTER="production-oidc" USER_NAME="zitadel" CONTEXT="production-oidc" say() { printf '%s\n' "$*" >&2; } die() { say "ошибка: $*"; exit 1; } need() { command -v "$1" >/dev/null 2>&1 || die "нужна команда $1"; } need curl need uname case "$(uname -s)" in Linux) os=linux ;; Darwin) os=darwin ;; *) die "поддерживаются Linux и macOS; на Windows — WSL" ;; esac case "$(uname -m)" in x86_64|amd64) arch=amd64 ;; aarch64|arm64) arch=arm64 ;; *) die "архитектура $(uname -m) не поддерживается" ;; esac sha256_check() { # файл ожидаемая_сумма if command -v sha256sum >/dev/null 2>&1; then got=$(sha256sum "$1" | cut -d' ' -f1) else got=$(shasum -a 256 "$1" | cut -d' ' -f1); fi [ "$got" = "$2" ] || die "контрольная сумма $1 не сошлась" } # 0 — версия $1 не ниже $2 (обе вида X.Y.Z) version_ge() { [ "$(printf '%s\n%s\n' "$2" "$1" | sort -t. -k1,1n -k2,2n -k3,3n | head -n1)" = "$2" ] } tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT trap 'exit 130' INT TERM mkdir -p "$BIN_DIR" # Системный kubectl годится, только если знает --exec-interactive-mode (kubectl 1.30+). if command -v kubectl >/dev/null 2>&1 && kubectl config set-credentials --help 2>/dev/null | grep -q exec-interactive-mode; then KUBECTL=kubectl say "kubectl уже есть: $(command -v kubectl)" else v="${KUBECTL_VERSION:-$(curl -fsSL https://dl.k8s.io/release/stable.txt)}" say "ставлю kubectl $v в $BIN_DIR" url="https://dl.k8s.io/release/$v/bin/$os/$arch/kubectl" curl -fsSL -o "$tmp/kubectl" "$url" sha256_check "$tmp/kubectl" "$(curl -fsSL "$url.sha256" | cut -d' ' -f1)" install -m 0755 "$tmp/kubectl" "$BIN_DIR/kubectl" KUBECTL="$BIN_DIR/kubectl" fi have="" if command -v kubelogin >/dev/null 2>&1; then have=$(kubelogin --version 2>/dev/null | sed -n 's/.*v\([0-9][0-9.]*\).*/\1/p' | head -n1) fi if [ -n "$have" ] && version_ge "$have" "$KUBELOGIN_MIN"; then say "kubelogin уже есть: v$have" KUBELOGIN=kubelogin else need unzip say "ставлю kubelogin $KUBELOGIN_VERSION в $BIN_DIR" zip="kubelogin_${os}_${arch}.zip" url="https://github.com/int128/kubelogin/releases/download/$KUBELOGIN_VERSION/$zip" curl -fsSL -o "$tmp/$zip" "$url" if [ "$KUBELOGIN_VERSION" = v1.36.4 ]; then eval "sum=\$KUBELOGIN_SHA256_${os}_${arch}" else sum=$(curl -fsSL "$url.sha256" | cut -d' ' -f1); fi sha256_check "$tmp/$zip" "$sum" unzip -oq "$tmp/$zip" kubelogin -d "$tmp" install -m 0755 "$tmp/kubelogin" "$BIN_DIR/kubelogin" # плагин kubectl: та же программа под именем kubectl-oidc_login ln -sf kubelogin "$BIN_DIR/kubectl-oidc_login" # абсолютный путь: старый kubelogin раньше в PATH не перехватит вызов KUBELOGIN="$BIN_DIR/kubelogin" fi case ":$PATH:" in *":$BIN_DIR:"*) ;; *) say "добавьте $BIN_DIR в PATH: export PATH=\"$BIN_DIR:\$PATH\"" ;; esac # Корневой сертификат кластера (iconicompany-ca, публичный). # SHA-256: A0:89:19:78:0D:DD:CA:4F:4A:F0:A9:F6:89:52:D6:37:9D:70:60:C3:0B:98:D3:96:C6:1A:AA:3F:D0:29:58:38 cat > "$tmp/ca.pem" <<'PEM' -----BEGIN CERTIFICATE----- MIICHTCCAaSgAwIBAgIUYy/zwBwM62QGYNHFajyEd75yNlYwCgYIKoZIzj0EAwMw PjELMAkGA1UEBhMCcnUxFTATBgNVBAoMDGljb25pY29tcGFueTEYMBYGA1UEAwwP aWNvbmljb21wYW55LWNhMB4XDTI0MDIxNjAzNTA1MFoXDTQ5MDIxNjAzNTA1MFow PjELMAkGA1UEBhMCcnUxFTATBgNVBAoMDGljb25pY29tcGFueTEYMBYGA1UEAwwP aWNvbmljb21wYW55LWNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhjGXfkHiGH3u TBHaB/otAU0Vq+0w43skF35niqdnnoJKCILxTF7jYRLQX93i4HTR6X6n4jdaD0wN 53Q7S0xDZHTXxGfYx0osomUYHUXfRvgU3iF1La8emxWLznzpEcKJo2MwYTAdBgNV HQ4EFgQUkszEkrw4A0qzwinSzVyvH7IoOaEwHwYDVR0jBBgwFoAUkszEkrw4A0qz winSzVyvH7IoOaEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwCgYI KoZIzj0EAwMDZwAwZAIwfWM9wXcodQL6W0uWw83D3Bh0iGtDkvrbKCd+2y/J1Zll /KvzvGhhcriNQM/rNFB9AjBEZDh2ScD01070cxRAxqbbSMaRvJjbmLU2gX4ozl8Q 235tFglbjWopOQf4u85/3E4= -----END CERTIFICATE----- PEM # set-cluster/set-credentials/set-context меняют только записи с этими именами. # interactiveMode обязателен с kubectl 1.37: без него exec-плагин не запускается. # KUBECONFIG может быть списком через «:». Новые записи kubectl кладёт в первый существующий # файл списка, а если таких нет — создаёт последний. Бэкап и права — только у этого файла. if [ -z "${KUBECONFIG:-}" ]; then KUBECONFIG="$HOME/.kube/config"; fi export KUBECONFIG cfg=""; last="" old_ifs=$IFS; IFS=: for f in $KUBECONFIG; do [ -n "$f" ] || continue last=$f if [ -z "$cfg" ] && [ -f "$f" ]; then cfg=$f; fi done IFS=$old_ifs [ -n "$cfg" ] || cfg=$last [ -n "$cfg" ] || die "KUBECONFIG пуст" mkdir -p "$(dirname "$cfg")" [ -f "$cfg" ] && cp "$cfg" "$cfg.bak.$(date +%Y%m%d%H%M%S)" # --kubeconfig: kubectl со списком берёт блокировку на каждый файл и падает на чужом нечитаемом kc() { "$KUBECTL" --kubeconfig="$cfg" "$@"; } kc config set-cluster "$CLUSTER" --server="$SERVER" \ --certificate-authority="$tmp/ca.pem" --embed-certs=true >/dev/null kc config set-credentials "$USER_NAME" \ --exec-api-version=client.authentication.k8s.io/v1 \ --exec-command="$KUBELOGIN" \ --exec-interactive-mode=IfAvailable \ --exec-arg=get-token \ --exec-arg=--oidc-issuer-url="$ISSUER" \ --exec-arg=--oidc-client-id="$CLIENT_ID" \ --exec-arg=--oidc-extra-scope=urn:zitadel:iam:org:projects:roles >/dev/null kc config set-context "$CONTEXT" --cluster="$CLUSTER" --user="$USER_NAME" >/dev/null chmod 600 "$cfg" say "" say "готово: контекст $CONTEXT в $cfg" say "проверка (откроется браузер для входа):" say " $KUBECTL --context $CONTEXT get ns" say "без браузера (по SSH): $KUBELOGIN get-token --oidc-issuer-url=$ISSUER --oidc-client-id=$CLIENT_ID --oidc-extra-scope=urn:zitadel:iam:org:projects:roles --grant-type=device-code" } main "$@"