Skip to content

Instantly share code, notes, and snippets.

@linktohack
Created September 4, 2025 14:31
Show Gist options
  • Save linktohack/80ffd766e1cff63ff74e44c371452de1 to your computer and use it in GitHub Desktop.
Save linktohack/80ffd766e1cff63ff74e44c371452de1 to your computer and use it in GitHub Desktop.
create_kubeconfig_with_ns.zsh
#!/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