Created
September 4, 2025 14:31
-
-
Save linktohack/80ffd766e1cff63ff74e44c371452de1 to your computer and use it in GitHub Desktop.
create_kubeconfig_with_ns.zsh
This file contains hidden or 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
| #!/usr/bin/env zsh | |
| # Purpose: Create a kubeconfig for a ServiceAccount with access to multiple namespaces | |
| # and permission to list namespaces cluster-wide. | |
| # Shell: Zsh (arrays are 1-indexed!) | |
| set -euo pipefail | |
| ### ===================== | |
| ### Configurable variables | |
| ### ===================== | |
| # Namespaces you want to grant access to (contexts will be created for each) | |
| typeset -a NS_LIST | |
| NS_LIST=(gitlab default) | |
| # ServiceAccount name and the namespace where it LIVES (must be one explicit namespace) | |
| SA="gitlab-sa" | |
| SA_NS="gitlab" # <-- change to where you want the SA to live | |
| # Split permissions per namespace (optional). If you want all RW, just put them all in RW_NAMESPACES | |
| # and leave RO_NAMESPACES empty. | |
| typeset -a RW_NAMESPACES | |
| RW_NAMESPACES=(gitlab default) | |
| typeset -a RO_NAMESPACES | |
| RO_NAMESPACES=() | |
| # Output kubeconfig file | |
| KUBECONFIG_OUT="kubeconfig-gitlab.yaml" | |
| # Token duration (K8s >= 1.24) | |
| TOKEN_DURATION="8760h" # ~1 year | |
| # Whether to (re)create the reusable ClusterRoles | |
| CREATE_CLUSTERROLES=true | |
| ### ===================== | |
| ### Helpers | |
| ### ===================== | |
| function info() { print -P "%F{blue}[INFO]%f $*" } | |
| function warn() { print -P "%F{yellow}[WARN]%f $*" } | |
| function die() { print -P "%F{red}[ERR ]%f $*" >&2; exit 1 } | |
| command -v kubectl >/dev/null 2>&1 || die "kubectl not found in PATH" | |
| ### ===================== | |
| ### Ensure namespaces exist | |
| ### ===================== | |
| for ns in ${NS_LIST[@]}; do | |
| if ! kubectl get ns "$ns" >/dev/null 2>&1; then | |
| info "Creating namespace: $ns" | |
| kubectl create ns "$ns" | |
| else | |
| info "Namespace exists: $ns" | |
| fi | |
| done | |
| ### ===================== | |
| ### ServiceAccount | |
| ### ===================== | |
| if ! kubectl -n "$SA_NS" get sa "$SA" >/dev/null 2>&1; then | |
| info "Creating ServiceAccount $SA in $SA_NS" | |
| kubectl -n "$SA_NS" create sa "$SA" | |
| else | |
| info "ServiceAccount exists: $SA in $SA_NS" | |
| fi | |
| ### ===================== | |
| ### ClusterRoles (reusable) | |
| ### ===================== | |
| if [[ "$CREATE_CLUSTERROLES" == true ]]; then | |
| info "Applying ClusterRole: app-rw" | |
| cat <<'YAML' | kubectl apply -f - | |
| apiVersion: rbac.authorization.k8s.io/v1 | |
| kind: ClusterRole | |
| metadata: | |
| name: app-rw | |
| rules: | |
| - apiGroups: ["", "apps", "batch", "extensions", "networking.k8s.io"] | |
| resources: ["*"] | |
| verbs: ["*"] | |
| YAML | |
| info "Applying ClusterRole: app-ro" | |
| cat <<'YAML' | kubectl apply -f - | |
| apiVersion: rbac.authorization.k8s.io/v1 | |
| kind: ClusterRole | |
| metadata: | |
| name: app-ro | |
| rules: | |
| - apiGroups: ["", "apps", "batch", "extensions", "networking.k8s.io"] | |
| resources: ["*"] | |
| verbs: ["get", "list", "watch"] | |
| YAML | |
| info "Applying ClusterRole: list-namespaces-only" | |
| cat <<'YAML' | kubectl apply -f - | |
| apiVersion: rbac.authorization.k8s.io/v1 | |
| kind: ClusterRole | |
| metadata: | |
| name: list-namespaces-only | |
| rules: | |
| - apiGroups: [""] | |
| resources: ["namespaces"] | |
| verbs: ["get", "list", "watch"] | |
| YAML | |
| fi | |
| ### ===================== | |
| ### RoleBindings per namespace | |
| ### ===================== | |
| for ns in ${RW_NAMESPACES[@]}; do | |
| info "Binding RW in $ns to SA $SA_NS:$SA" | |
| kubectl -n "$ns" create rolebinding "${SA}-rw" \ | |
| --clusterrole=app-rw \ | |
| --serviceaccount="${SA_NS}:${SA}" \ | |
| --dry-run=client -o yaml | kubectl apply -f - | |
| done | |
| for ns in ${RO_NAMESPACES[@]}; do | |
| info "Binding RO in $ns to SA $SA_NS:$SA" | |
| kubectl -n "$ns" create rolebinding "${SA}-ro" \ | |
| --clusterrole=app-ro \ | |
| --serviceaccount="${SA_NS}:${SA}" \ | |
| --dry-run=client -o yaml | kubectl apply -f - | |
| done | |
| ### ===================== | |
| ### ClusterRoleBinding to allow listing namespaces | |
| ### ===================== | |
| info "Binding ClusterRole list-namespaces-only" | |
| kubectl create clusterrolebinding "${SA}-can-list-ns" \ | |
| --clusterrole=list-namespaces-only \ | |
| --serviceaccount="${SA_NS}:${SA}" \ | |
| --dry-run=client -o yaml | kubectl apply -f - | |
| ### ===================== | |
| ### Token retrieval (K8s >= 1.24) with fallback for older clusters | |
| ### ===================== | |
| function get_token() { | |
| set +e | |
| local t | |
| t=$(kubectl -n "$SA_NS" create token "$SA" --duration="$TOKEN_DURATION" 2>/dev/null) | |
| local rc=$? | |
| set -e | |
| if [[ $rc -eq 0 && -n "$t" ]]; then | |
| print -- "$t" | |
| return 0 | |
| fi | |
| warn "kubectl create token failed; falling back to legacy secret-based token" | |
| local secret | |
| secret=$(kubectl -n "$SA_NS" get sa "$SA" -o jsonpath='{.secrets[0].name}' 2>/dev/null || true) | |
| [[ -z "$secret" ]] && die "No secret found on SA; your cluster may require enabling legacy token secrets or using a newer kubectl." | |
| kubectl -n "$SA_NS" get secret "$secret" -o jsonpath='{.data.token}' | base64 -d | |
| } | |
| TOKEN=$(get_token) | |
| [[ -z "$TOKEN" ]] && die "Failed to obtain a token for $SA in $SA_NS" | |
| ### ===================== | |
| ### Cluster connection info | |
| ### ===================== | |
| SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}') | |
| CA_DATA=$(kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}') | |
| [[ -z "$SERVER" || -z "$CA_DATA" ]] && die "Failed to read current cluster server/CA from kubeconfig" | |
| ### ===================== | |
| ### Build kubeconfig with contexts for each namespace in NS_LIST | |
| ### ===================== | |
| info "Writing kubeconfig to $KUBECONFIG_OUT" | |
| { | |
| cat <<EOF | |
| apiVersion: v1 | |
| kind: Config | |
| clusters: | |
| - name: target-cluster | |
| cluster: | |
| server: ${SERVER} | |
| certificate-authority-data: ${CA_DATA} | |
| users: | |
| - name: ${SA} | |
| user: | |
| token: ${TOKEN} | |
| contexts: | |
| EOF | |
| for ns in ${NS_LIST[@]}; do | |
| cat <<EOF | |
| - name: ${SA}-${ns} | |
| context: | |
| cluster: target-cluster | |
| user: ${SA} | |
| namespace: ${ns} | |
| EOF | |
| done | |
| cat <<EOF | |
| current-context: ${SA}-${NS_LIST[1]} | |
| EOF | |
| } > "$KUBECONFIG_OUT" | |
| ### ===================== | |
| ### Summary | |
| ### ===================== | |
| print "\nDone. Kubeconfig: $KUBECONFIG_OUT" | |
| print "Use it with:" | |
| print " export KUBECONFIG=\"$PWD/$KUBECONFIG_OUT\"" | |
| print " kubectl config get-contexts" | |
| print " kubectl get ns # should be allowed" | |
| print " kubectl get pods # acts in current-context namespace (${NS_LIST[1]})" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment