为指定用户生成不同权限的 kubeconfig

脚本: gen-kubeconfig.sh

背景

K8s 集群跑起来之后,迟早会有人来找你要 kubeconfig:排查问题的、部署应用的、跨环境操作的。直接把 admin 的 kubeconfig 复制一份发出去等于把集群钥匙交出去了,正确做法是给每个人单独建 ServiceAccount、按需授权、再生成一份独立的 kubeconfig。

但手动做这套流程步骤不少:建 SA、建 token Secret、建 Binding、等 token 写入、取 token、取集群 server/CA、用 kubectl config set-* 组装 kubeconfig。而且不同人需要的权限组合还不一样,每次手敲又繁琐又容易出错。

gen-kubeconfig.sh 把这套流程封装成一个脚本:一条命令,按需生成不同权限、不同范围的 kubeconfig。

两个正交维度

权限拆成两个正交维度,组合出 4 种情况:

维度取值
权限readonly (默认) / readwrite
范围集群级 (默认) / ns 级 (--namespace)
命令权限范围Role
--ro只读集群readonly-all (自定义 ClusterRole)
--rw全权集群cluster-admin (内置)
--ro --ns prod只读nsview (内置)
--rw --ns prod全权nsadmin (内置)

其中 readonly-all 是脚本自动创建的共享 ClusterRole(内置的 view 对非资源 URL、*/proxy 之类覆盖不全),所有"集群只读"用户都绑定它,删除用户时也不会被删掉:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: readonly-all
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["*"]
  resources: ["*/proxy"]
  verbs: ["get"]
- nonResourceURLs: ["*"]
  verbs: ["get"]

资源命名与 label

每个用户在集群里留下一组资源,命名统一:<perm-prefix>-<user>,前缀 ro- 表示只读、rw- 表示全权。比如用户 zhangsan 同时有 ro 和 rw 两套权限,集群里就有 ro-zhangsan / rw-zhangsan 两套 SA,互不影响。

所有脚本创建的资源都打上 label:

labels:
  managed-by: gen-kubeconfig
  user: <user>
  perm: readonly|readwrite
  scope: cluster|namespace

这组 label 是 delete 子命令精准清理的依据——脚本不会碰任何没有这组 label 的资源。

用法

./gen-kubeconfig.sh <user> [权限选项] [范围选项] [其他选项]   # 生成
./gen-kubeconfig.sh delete <user> [权限选项]                  # 删除
选项等价说明
--readonly--ro(默认) 只读,SA 前缀 ro-
--readwrite--rw完全权限,SA 前缀 rw-
--namespace <ns[,ns...]>--nsns 级,可逗号分隔或多次指定
--cluster-name <name>--cnkubeconfig 中的集群名(默认: kubernetes)
--output <file>-o写入指定文件(不指定则输出到 stdout)

例子

# 集群只读(最常见,给排查问题的人)
./gen-kubeconfig.sh zhangsan -o ~/.kube/kc-zhangsan

# 集群全权(慎用,相当于 cluster-admin)
./gen-kubeconfig.sh zhangsan --rw -o ~/.kube/kc-zhangsan

# prod ns 只读
./gen-kubeconfig.sh zhangsan --ro --ns prod -o ~/.kube/kc-zhangsan

# prod + staging ns 全权(逗号分隔或多次指定都行)
./gen-kubeconfig.sh zhangsan --rw --ns prod,staging -o ~/.kube/kc-zhangsan
./gen-kubeconfig.sh zhangsan --rw --ns prod --ns staging -o ~/.kube/kc-zhangsan

# 自定义集群名(多套集群时方便区分 context)
./gen-kubeconfig.sh zhangsan --rw --cn my-cluster -o ~/.kube/kc-zhangsan

验证

# 集群只读:能看不能动
KUBECONFIG=~/.kube/kc-zhangsan kubectl get pods -A    # ✓
KUBECONFIG=~/.kube/kc-zhangsan kubectl delete pod x   # ✗ Forbidden

# ns 全权:在自己 ns 里随便操作,跨 ns / 集群级被拒
KUBECONFIG=~/.kube/kc-zhangsan kubectl -n prod get pods    # ✓
KUBECONFIG=~/.kube/kc-zhangsan kubectl -n prod delete pod x # ✓
KUBECONFIG=~/.kube/kc-zhangsan kubectl get nodes            # ✗ Forbidden

删除

./gen-kubeconfig.sh delete zhangsan        # 删除该用户所有资源(ro+rw)
./gen-kubeconfig.sh delete zhangsan --ro   # 只删除只读
./gen-kubeconfig.sh delete zhangsan --rw   # 只删除全权

delete 基于 label selector 精准匹配,只删该用户名下、由本脚本创建的资源,共享的 readonly-all ClusterRole 和 kube-system ns 都会保留。

几个安全设计

  • 不污染当前 kubeconfig:所有 kubectl config set-* 都指向临时文件,输出后删除;当前环境的 kubeconfig 和 current-context 不会被改。如果 -o 指定的路径恰好是当前正在用的 kubeconfig,脚本会拒绝。
  • 显式锁定原始 KUBECONFIG:读取集群 server/CA 时显式用启动时记录的 ORIG_KUBECONFIG,避免中途 $KUBECONFIG 变化取错集群。
  • 输出文件 600 权限:避免其他用户读到 token。
  • 用户名严格校验:只允许 [a-z0-9-]、以字母开头、以字母或数字结尾、长度 ≤ 63,符合 K8s 资源命名规则。
  • 幂等:所有 kubectl apply 幂等,同一用户同权限重复跑不会创建重复资源。

完整脚本

#!/usr/bin/env bash
#
# gen-kubeconfig.sh - 为指定用户生成/删除 kubeconfig
#
# 用法:
#   ./gen-kubeconfig.sh <user> [权限选项] [范围选项] [--cluster-name|--cn <name>] [--output|-o <file>]
#       生成 kubeconfig,输出到 stdout 或指定文件
#   ./gen-kubeconfig.sh delete <user> [--readonly|--ro|--readwrite|--rw]
#       删除该用户的资源(不指定权限则删除该用户所有资源)
#
# 两个正交维度:
#   权限:  --readonly / --ro  (默认) 只读  /  --readwrite / --rw  完全权限
#   范围:  不指定 = 集群级     /  --namespace / --ns <ns...> = 指定 ns 级
#
# 注意: 同一用户可以同时拥有 ro 和 rw 权限(SA 名不同: ro-<user> / rw-<user>,互不影响)
#
# 组合:
#   --readonly                                  集群只读   (ClusterRole: readonly-all)
#   --readwrite                                 集群全权   (ClusterRole: cluster-admin)
#   --readonly  --namespace prod[,staging]       ns 只读    (Role: view)
#   --readwrite --namespace prod[,staging]       ns 全权    (Role: admin)
#
# SA 命名:
#   readonly  -> ro-<user>    (read-only)
#   readwrite -> rw-<user>    (read-write)
#
# 共享资源(不会删除):
#   - ClusterRole: readonly-all
#   - Namespace:   kube-system (SA 所在)
#
# 用户专属资源(gen 创建 / delete 清理,均带 label: managed-by=gen-kubeconfig,user=<user>):
#   - ServiceAccount:      <prefix>-<user>          (namespace: kube-system)
#   - Secret (token):      <prefix>-<user>-token    (namespace: kube-system)
#   - ClusterRoleBinding:  <prefix>-<user>-binding  (集群级模式)
#   - RoleBinding:         <prefix>-<user>-binding  (ns 级模式, 每个目标 ns 一个)
#   - kubeconfig:          直接输出到 stdout(不落盘)
#
# 安全保证:
#   - 脚本不会修改当前环境的 kubeconfig 文件或 current-context
#   - 所有 kubectl config 写操作都指向临时文件,输出后删除
#   - 读取集群连接信息时显式使用脚本启动时的 KUBECONFIG(只读)
#   - kubeconfig 只输出到 stdout,由调用方决定保存位置
#
set -euo pipefail

# ============== 可配置项 ==============
NS="kube-system"                       # SA 所在命名空间(共享,不删除)
CLUSTER_ROLE_READONLY="readonly-all"   # 共享的只读 ClusterRole(不删除)
CLUSTER_NAME="kubernetes"             # kubeconfig 中的集群名(默认,可被 --cluster-name 覆盖)
# ====================================

# ---- 环境隔离保护 ----
ORIG_KUBECONFIG="${KUBECONFIG:-}"
CURRENT_KUBECONFIG_FILE=""
if [[ -n "${ORIG_KUBECONFIG}" ]]; then
  CURRENT_KUBECONFIG_FILE="${ORIG_KUBECONFIG%%:*}"
else
  CURRENT_KUBECONFIG_FILE="${HOME}/.kube/config"
fi

# 共享 ClusterRole 定义(首次执行时创建,已存在则跳过)
readonly_CLUSTERROLE=$(cat <<'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: readonly-all
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["*"]
  resources: ["*/proxy"]
  verbs: ["get"]
- nonResourceURLs: ["*"]
  verbs: ["get"]
EOF
)

err()  { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" >&2; exit 1; }
info() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]  $*" >&2; }

# 根据权限返回 SA 前缀
sa_prefix() {
  case "$1" in
    readonly)  echo "ro" ;;
    readwrite) echo "rw" ;;
    *) err "内部错误: 未知权限 '${1}'" ;;
  esac
}

# 校验用户名:只允许 [a-z0-9-],避免非法 K8s 资源名/label value
validate_user() {
  local u="$1"
  [[ -n "${u}" ]] || err "用户名不能为空"
  [[ "${u}" =~ ^[a-z]([a-z0-9-]*[a-z0-9])?$ ]] \
    || err "用户名只能包含小写字母/数字/连字符,且以字母开头、以字母或数字结尾: ${u}"
  [[ ${#u} -le 63 ]] || err "用户名过长(>63): ${u}"
}

# 校验 namespace 名
validate_ns() {
  local n="$1"
  [[ -n "${n}" ]] || err "namespace 不能为空"
  [[ "${n}" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]] \
    || err "namespace 名非法: ${n}"
  [[ ${#n} -le 63 ]] || err "namespace 名过长(>63): ${n}"
}

# 确保共享 ClusterRole 存在(幂等)—— 集群只读模式需要
ensure_clusterrole() {
  if kubectl get clusterrole "${CLUSTER_ROLE_READONLY}" >/dev/null 2>&1; then
    info "共享 ClusterRole '${CLUSTER_ROLE_READONLY}' 已存在,跳过创建"
  else
    info "创建共享 ClusterRole '${CLUSTER_ROLE_READONLY}'"
    echo "${readonly_CLUSTERROLE}" | kubectl apply -f -
  fi
}

# 确保命名空间存在(幂等)
ensure_namespace() {
  local target_ns="$1"
  if kubectl get ns "${target_ns}" >/dev/null 2>&1; then
    return 0
  fi
  info "创建命名空间 '${target_ns}'"
  kubectl create namespace "${target_ns}"
}

# 查找该用户某权限下已有的 SA 名(基于 label),返回 SA 名或空
find_user_sa() {
  local user="$1" perm="$2"
  kubectl -n "${NS}" get serviceaccount \
    -l managed-by=gen-kubeconfig,user="${user}",perm="${perm}" \
    -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true
}

# 清理该用户某权限下的所有 binding(基于 label selector)
# 参数: user perm  —— perm 为空时清理该用户所有权限的 binding
cleanup_old_bindings() {
  local user="$1" perm="${2:-}"
  local label="managed-by=gen-kubeconfig,user=${user}"
  [[ -n "${perm}" ]] && label="${label},perm=${perm}"

  # ClusterRoleBinding
  kubectl delete clusterrolebinding \
    -l "${label}" \
    --ignore-not-found=true 2>/dev/null || true

  # RoleBinding(跨所有 ns 查找)
  local rbs
  rbs=$(kubectl get rolebinding -A \
    -l "${label}" \
    -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
  if [[ -n "${rbs}" ]]; then
    while IFS= read -r rb; do
      [[ -z "${rb}" ]] && continue
      local rb_ns="${rb%%/*}"
      local rb_name="${rb##*/}"
      kubectl -n "${rb_ns}" delete rolebinding "${rb_name}" --ignore-not-found=true 2>/dev/null || true
    done <<< "${rbs}"
  fi
}

# 清理该用户的 SA 和 Secret(基于 label)
# 参数: user perm  —— perm 为空时清理该用户所有权限的 SA/Secret
cleanup_user_sa_secret() {
  local user="$1" perm="${2:-}"
  local label="managed-by=gen-kubeconfig,user=${user}"
  [[ -n "${perm}" ]] && label="${label},perm=${perm}"

  kubectl -n "${NS}" delete secret \
    -l "${label}" \
    --ignore-not-found=true 2>/dev/null || true
  kubectl -n "${NS}" delete serviceaccount \
    -l "${label}" \
    --ignore-not-found=true 2>/dev/null || true
}

# 创建 binding
# 参数: user perm scope sa namespaces...
create_binding() {
  local user="$1" perm="$2" scope="$3" sa="$4"
  shift 4
  local namespaces=()
  [[ $# -gt 0 ]] && namespaces=("$@")
  local binding="${sa}-binding"

  cleanup_old_bindings "${user}" "${perm}"

  if [[ "${scope}" == "cluster" ]]; then
    # ---- 集群级 ----
    local role_name
    if [[ "${perm}" == "readonly" ]]; then
      role_name="${CLUSTER_ROLE_READONLY}"
      info "创建 ClusterRoleBinding (集群只读, role=${role_name}) -> ${binding}"
    else
      role_name="cluster-admin"
      info "创建 ClusterRoleBinding (集群全权, role=${role_name}) -> ${binding}"
    fi
    kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: ${binding}
  labels:
    managed-by: gen-kubeconfig
    user: ${user}
    perm: ${perm}
    scope: cluster
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ${role_name}
subjects:
- kind: ServiceAccount
  name: ${sa}
  namespace: ${NS}
EOF
  else
    # ---- namespace 级 ----
    local role_name
    if [[ "${perm}" == "readonly" ]]; then
      role_name="view"
    else
      role_name="admin"
    fi
    local target_ns
    for target_ns in ${namespaces[@]+"${namespaces[@]}"}; do
      info "创建 RoleBinding (ns只读/全权=${perm}, role=${role_name}@${target_ns}) -> ${binding}"
      kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ${binding}
  namespace: ${target_ns}
  labels:
    managed-by: gen-kubeconfig
    user: ${user}
    perm: ${perm}
    scope: namespace
    target-namespace: ${target_ns}
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ${role_name}
subjects:
- kind: ServiceAccount
  name: ${sa}
  namespace: ${NS}
EOF
    done
  fi
}

# 生成/刷新某用户的 kubeconfig
# 参数: user perm scope cluster_name output_file namespaces...
#   output_file 为空时输出到 stdout
do_gen() {
  local user="$1" perm="$2" scope="$3" cluster_name="$4" output_file="$5"
  shift 5
  local namespaces=()
  [[ $# -gt 0 ]] && namespaces=("$@")
  local prefix
  prefix=$(sa_prefix "${perm}")
  local sa="${prefix}-${user}"
  local secret="${sa}-token"
  local user_ctx="${sa}@${cluster_name}"
  local out ca_file
  out=$(mktemp -t kubeconfig-"${user}".XXXXXX)
  ca_file=""
  trap 'rm -f "${out}" "${ca_file}"' EXIT

  validate_user "${user}"

  # 准备与校验
  ensure_namespace "${NS}"
  if [[ "${scope}" == "cluster" ]]; then
    if [[ "${perm}" == "readonly" ]]; then
      ensure_clusterrole
      info "权限: 集群只读 (readonly)  范围: 集群级  SA=${sa}"
    else
      info "权限: 集群全权 (readwrite)  范围: 集群级  SA=${sa}"
    fi
  else
    [[ ${#namespaces[@]:-0} -gt 0 ]] || err "namespace 模式需要至少指定一个 ns"
    local n
    for n in ${namespaces[@]+"${namespaces[@]}"}; do
      validate_ns "${n}"
      ensure_namespace "${n}"
    done
    if [[ "${perm}" == "readonly" ]]; then
      info "权限: ns 只读 (readonly)  范围: ns=${namespaces[*]:-}  SA=${sa}"
    else
      info "权限: ns 全权 (readwrite)  范围: ns=${namespaces[*]:-}  SA=${sa}"
    fi
  fi

  # 检查同权限下是否已有旧 SA(scope 切换时需要清理旧 binding)
  # 注意:ro 和 rw 可以共存,不互删;只清理同 perm 的旧 binding
  local old_sa
  old_sa=$(find_user_sa "${user}" "${perm}")
  if [[ -n "${old_sa}" && "${old_sa}" != "${sa}" ]]; then
    info "检测到同权限下 SA 名变更 (旧: ${old_sa} -> 新: ${sa}),清理旧资源"
    cleanup_old_bindings "${user}" "${perm}"
    cleanup_user_sa_secret "${user}" "${perm}"
  fi

  info "为用户 '${user}' 准备资源: SA=${sa}, Secret=${secret}"

  # 1) ServiceAccount(幂等:apply,带 label)
  kubectl -n "${NS}" apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ${sa}
  namespace: ${NS}
  labels:
    managed-by: gen-kubeconfig
    user: ${user}
    perm: ${perm}
    scope: ${scope}
EOF

  # 2) Token Secret(幂等:apply;如已存在会保留旧 token)
  kubectl -n "${NS}" apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: ${secret}
  namespace: ${NS}
  labels:
    managed-by: gen-kubeconfig
    user: ${user}
  annotations:
    kubernetes.io/service-account.name: ${sa}
type: kubernetes.io/service-account-token
EOF

  # 3) Binding
  create_binding "${user}" "${perm}" "${scope}" "${sa}" ${namespaces[@]+"${namespaces[@]}"}

  # 4) 等待 token 写入 Secret
  info "等待 token 写入 Secret '${secret}'..."
  local token=""
  for _ in $(seq 1 30); do
    token=$(kubectl -n "${NS}" get secret "${secret}" \
              -o jsonpath='{.data.token}' 2>/dev/null | base64 -d 2>/dev/null || true)
    [[ -n "${token}" ]] && break
    sleep 1
  done
  [[ -n "${token}" ]] || err "获取 token 失败,请检查 Secret '${secret}'"

  # 5) 从当前 kubeconfig 取集群连接信息(只读,显式使用原始 KUBECONFIG)
  local server ca
  server=$(KUBECONFIG="${ORIG_KUBECONFIG}" kubectl config view --raw -o jsonpath='{.clusters[0].cluster.server}')
  ca=$(KUBECONFIG="${ORIG_KUBECONFIG}" kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
  [[ -n "${server}" ]] || err "无法从当前 kubeconfig 获取 server 地址"
  [[ -n "${ca}" ]]     || err "无法从当前 kubeconfig 获取 CA 证书"

  # kubectl config set-cluster 不支持 --certificate-authority-data,
  # 需要先把 base64 的 CA 解码写入临时文件,再用 --certificate-authority + --embed-certs
  ca_file=$(mktemp -t ca.XXXXXX)
  echo "${ca}" | base64 -d > "${ca_file}"

  # 6) 组装该用户的 kubeconfig 到临时文件
  kubectl config set-cluster "${cluster_name}" \
    --server="${server}" \
    --certificate-authority="${ca_file}" \
    --kubeconfig="${out}" --embed-certs=true >/dev/null

  kubectl config set-credentials "${user_ctx}" \
    --token="${token}" \
    --kubeconfig="${out}" >/dev/null

  # ns 级模式时,context 默认指向第一个 ns
  if [[ "${scope}" == "namespace" ]]; then
    kubectl config set-context "${user_ctx}" \
      --cluster="${cluster_name}" \
      --user="${user_ctx}" \
      --namespace="${namespaces[0]:-}" \
      --kubeconfig="${out}" >/dev/null
  else
    kubectl config set-context "${user_ctx}" \
      --cluster="${cluster_name}" \
      --user="${user_ctx}" \
      --kubeconfig="${out}" >/dev/null
  fi

  kubectl config use-context "${user_ctx}" --kubeconfig="${out}" >/dev/null
  chmod 600 "${out}"

  rm -f "${ca_file}"

  # 7) 输出 kubeconfig
  if [[ -n "${output_file}" ]]; then
    # 写入指定文件
    cp "${out}" "${output_file}"
    chmod 600 "${output_file}"
    info "✅ 用户 '${user}' 的 kubeconfig 已生成 (perm=${perm}, scope=${scope}, SA=${sa})"
    info "   用户标识: ${user_ctx}"
    if [[ "${scope}" == "namespace" ]]; then
      info "   授权 namespace: ${namespaces[*]:-}"
      info "   context 默认 ns: ${namespaces[0]:-}"
    fi
    info "   已保存到: ${output_file}"
    info "   验证: KUBECONFIG=${output_file} kubectl get pods -A"
  else
    # 输出到 stdout(纯净 kubeconfig,无分隔线,可直接重定向)
    info "✅ 用户 '${user}' 的 kubeconfig 已生成 (perm=${perm}, scope=${scope}, SA=${sa})"
    info "   用户标识: ${user_ctx}"
    if [[ "${scope}" == "namespace" ]]; then
      info "   授权 namespace: ${namespaces[*]:-}"
      info "   context 默认 ns: ${namespaces[0]:-}"
    fi
    info "   当前环境 kubeconfig: ${CURRENT_KUBECONFIG_FILE}(脚本未修改它)"
    info "   kubeconfig:"
    info "----------------------------- KUBECONFIG ------------------------------"
    cat "${out}"
  fi

  rm -f "${out}"
  trap - EXIT
}

# 删除某用户的专属资源(基于 label,保留共享 ClusterRole 和 namespace)
# 参数: user [perm]  —— perm 为空时删除该用户所有权限的资源
do_delete() {
  local user="$1" perm="${2:-}"
  validate_user "${user}"

  local label="managed-by=gen-kubeconfig,user=${user}"
  [[ -n "${perm}" ]] && label="${label},perm=${perm}"

  if [[ -n "${perm}" ]]; then
    info "删除用户 '${user}' 的 ${perm} 权限资源(保留共享 ClusterRole '${CLUSTER_ROLE_READONLY}')"
  else
    info "删除用户 '${user}' 的所有专属资源(保留共享 ClusterRole '${CLUSTER_ROLE_READONLY}')"
  fi

  # 删除顺序:binding -> Secret -> SA
  cleanup_old_bindings "${user}" "${perm}"
  cleanup_user_sa_secret "${user}" "${perm}"

  if [[ -n "${perm}" ]]; then
    info "✅ 用户 '${user}' 的 ${perm} 权限资源已清理"
  else
    info "✅ 用户 '${user}' 的所有专属资源已清理"
  fi
}

usage() {
  cat <<EOF
用法:
  $0 <user> [权限选项] [范围选项] [其他选项]   生成 kubeconfig
  $0 delete <user> [权限选项]                  删除该用户的资源

权限选项:
  --readonly, --ro                  (默认) 只读,SA 前缀 ro-
  --readwrite, --rw                 完全权限,SA 前缀 rw-

范围选项:
  (不指定)                           集群级
  --namespace, --ns <ns[,ns...]>     指定 ns 级,可逗号分隔或多次指定

其他选项:
  --cluster-name, --cn <name>        kubeconfig 中的集群名(默认: kubernetes)
  --output, -o <file>                写入指定文件(不指定则输出到 stdout)

注意: 同一用户可以同时拥有 ro 和 rw 权限(SA 名不同,互不影响)

组合示例:
  $0 zhangsan                                          # 集群只读 (ro-zhangsan)
  $0 zhangsan --rw                                     # 集群全权 (rw-zhangsan)
  $0 zhangsan --ro  --ns prod                          # prod 只读 (ro-zhangsan)
  $0 zhangsan --rw --ns prod                           # prod 全权 (rw-zhangsan)
  $0 zhangsan --rw --ns prod,staging                   # prod+staging 全权
  $0 zhangsan --rw --ns prod --ns staging              # 同上
  $0 zhangsan --rw --cn my-cluster                     # 指定集群名 my-cluster

保存与删除:
  $0 zhangsan --rw -o ~/.kube/kc-zhangsan              # 保存到文件
  $0 zhangsan --rw > ~/.kube/kc-zhangsan               # 重定向保存(stdout 纯净)
  $0 delete zhangsan                                   # 删除该用户所有资源(ro+rw)
  $0 delete zhangsan --ro                              # 只删除只读权限
  $0 delete zhangsan --rw                              # 只删除完全权限
EOF
}

main() {
  [[ $# -ge 1 ]] || { usage; exit 1; }

  # 当前 kubeconfig 必须有集群访问权限
  kubectl get ns >/dev/null 2>&1 || err "当前 kubeconfig 无集群访问权限,请先配置管理员 kubeconfig"

  case "$1" in
    delete)
      [[ $# -ge 2 ]] || { usage; exit 1; }
      local del_user="$2" del_perm=""
      shift 2
      while [[ $# -gt 0 ]]; do
        case "$1" in
          --readonly|--ro)  del_perm="readonly"; shift ;;
          --readwrite|--rw) del_perm="readwrite"; shift ;;
          *) err "delete 只支持 --readonly/--ro 或 --readwrite/--rw,未知参数: $1" ;;
        esac
      done
      do_delete "${del_user}" "${del_perm}"
      ;;
    -h|--help|help)
      usage
      ;;
    *)
      local user="$1" perm="readonly" cluster_name="${CLUSTER_NAME}" output_file=""
      local namespaces=()
      shift
      while [[ $# -gt 0 ]]; do
        case "$1" in
          --readonly|--ro)
            perm="readonly"; shift ;;
          --readwrite|--rw)
            perm="readwrite"; shift ;;
          --namespace|--ns)
            [[ $# -ge 2 ]] || err "--namespace 需要参数: <ns[,ns...]>"
            # 支持逗号分隔
            local IFS=','
            read -ra ns_arr <<< "$2"
            local ns_item
            for ns_item in "${ns_arr[@]}"; do
              # 去除首尾空格
              ns_item="${ns_item## }"
              ns_item="${ns_item%% }"
              [[ -n "${ns_item}" ]] && namespaces+=("${ns_item}")
            done
            shift 2
            ;;
          --cluster-name|--cn)
            [[ $# -ge 2 ]] || err "--cluster-name 需要参数: <name>"
            cluster_name="$2"
            shift 2
            ;;
          --output|-o)
            [[ $# -ge 2 ]] || err "--output 需要参数: <file>"
            output_file="$2"
            shift 2
            ;;
          *)
            err "未知参数: $1(可用: --readonly/--ro / --readwrite/--rw / --namespace/--ns <ns> / --cluster-name/--cn <name> / --output/-o <file>)"
            ;;
        esac
      done
      # 防止误覆盖当前 kubeconfig
      if [[ -n "${output_file}" ]]; then
        local abs_output
        abs_output=$(cd "$(dirname "${output_file}")" 2>/dev/null && echo "$(pwd)/$(basename "${output_file}")" || echo "${output_file}")
        if [[ "${abs_output}" == "${CURRENT_KUBECONFIG_FILE}" ]]; then
          err "拒绝写入当前正在使用的 kubeconfig: ${output_file}(避免覆盖当前环境配置)"
        fi
      fi
      local scope="cluster"
      [[ ${#namespaces[@]:-0} -gt 0 ]] && scope="namespace"
      do_gen "${user}" "${perm}" "${scope}" "${cluster_name}" "${output_file}" ${namespaces[@]+"${namespaces[@]}"}
      ;;
  esac
}

main "$@"