Ops/Kubernetes

K8s readonly kubeconfig 파일 생성하기

록흐 2026. 5. 14. 17:48
반응형

 

 

default 네임스페이스에 K8s Cluster 전역 조회 권한이 있는 ServiceAccount 생성 ( Secret 리소스 읽기 제외 )

apiVersion: v1
kind: ServiceAccount
metadata:
  name: read-only-no-secrets
  namespace: default
---
# Kubernetes RBAC는 명시적 허용만 가능(제외 불가)하므로
# core API group에서 secrets를 의도적으로 제외하고 나머지 리소스만 나열한다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: read-only-no-secrets
rules:
  - apiGroups: [""]
    resources:
      - pods
      - pods/log
      - pods/status
      - services
      - endpoints
      - configmaps
      - persistentvolumeclaims
      - persistentvolumes
      - namespaces
      - nodes
      - events
      - replicationcontrollers
      - resourcequotas
      - limitranges
      - serviceaccounts
      - bindings
      - componentstatuses
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["batch"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["autoscaling"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["policy"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["discovery.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apiextensions.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["scheduling.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["coordination.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["events.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["metrics.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["admissionregistration.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["certificates.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["node.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["flowcontrol.apiserver.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-only-no-secrets
subjects:
  - kind: ServiceAccount
    name: read-only-no-secrets
    namespace: default
roleRef:
  kind: ClusterRole
  name: read-only-no-secrets
  apiGroup: rbac.authorization.k8s.io

 

 

생성한 SA의 장기(만료 없음) 토큰 Secret 생성하기

apiVersion: v1
kind: Secret
metadata:
  name: read-only-no-secrets-token
  namespace: default
  annotations:
    kubernetes.io/service-account.name: read-only-no-secrets
type: kubernetes.io/service-account-token

 

 

kubeconfig 파일 생성하기

#!/usr/bin/env bash
set -euo pipefail

NAMESPACE="${NAMESPACE:-default}"
SECRET_NAME="${SECRET_NAME:-read-only-no-secrets-token}"
SA_NAME="${SA_NAME:-read-only-no-secrets}"
KUBECONFIG_OUT="${KUBECONFIG_OUT:-./readonly-kubeconfig.yaml}"

# 토큰 컨트롤러가 비동기로 채우므로 최대 ~10초 대기
TOKEN_B64=""
for i in 1 2 3 4 5; do
  TOKEN_B64=$(kubectl get secret "$SECRET_NAME" -n "$NAMESPACE" \
    -o jsonpath='{.data.token}' 2>/dev/null || true)
  if [ -n "$TOKEN_B64" ]; then break; fi
  echo "토큰이 아직 채워지지 않음 ($i/5), 2초 대기..." >&2
  sleep 2
done

if [ -z "$TOKEN_B64" ]; then
  echo "ERROR: $NAMESPACE/$SECRET_NAME 에 토큰이 없습니다." >&2
  echo "  - Secret이 존재하는지: kubectl get secret $SECRET_NAME -n $NAMESPACE" >&2
  echo "  - 어노테이션이 SA를 가리키는지 확인하세요." >&2
  exit 1
fi

TOKEN=$(printf '%s' "$TOKEN_B64" | base64 -d)
CA=$(kubectl get secret "$SECRET_NAME" -n "$NAMESPACE" -o jsonpath='{.data.ca\.crt}')

SERVER=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.server}')
CLUSTER=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].name}')

if [ -z "$CA" ] || [ -z "$SERVER" ] || [ -z "$CLUSTER" ]; then
  echo "ERROR: CA / server / cluster 중 일부를 가져오지 못했습니다." >&2
  echo "  CA len=${#CA} SERVER=$SERVER CLUSTER=$CLUSTER" >&2
  exit 1
fi

umask 077
cat > "$KUBECONFIG_OUT" <<EOF
apiVersion: v1
kind: Config
clusters:
- name: ${CLUSTER}
  cluster:
    server: ${SERVER}
    certificate-authority-data: ${CA}
users:
- name: ${SA_NAME}
  user:
    token: ${TOKEN}
contexts:
- name: read-only
  context:
    cluster: ${CLUSTER}
    user: ${SA_NAME}
    namespace: ${NAMESPACE}
current-context: read-only
EOF
chmod 600 "$KUBECONFIG_OUT"

echo "✓ kubeconfig 생성: $KUBECONFIG_OUT"
echo ""
echo "테스트:"
echo "  KUBECONFIG=$KUBECONFIG_OUT kubectl get pods -A      # OK"
echo "  KUBECONFIG=$KUBECONFIG_OUT kubectl get secrets      # Forbidden 정상"
반응형