Service Account Token Yönetimi: Kubernetes ve CI/CD Pipeline Entegrasyonu
Kubernetes ortamlarında güvenlik açıklarının büyük çoğunluğu, yanlış yapılandırılmış ya da unutulmuş service account token’larından kaynaklanıyor. CI/CD pipeline’larınızda kullandığınız token’lar ne kadar süreyle geçerli? Kim yönetiyor bunları? Bu soruların cevabını vermekte zorlanıyorsanız, bu yazı tam size göre. Gerçek dünya senaryolarıyla service account token yönetimini ele alacağız.
Service Account Token Nedir ve Neden Önemlidir?
Kubernetes’te her pod, varsayılan olarak bir service account ile ilişkilendirilir. Bu service account, pod’un Kubernetes API sunucusuyla iletişim kurmasını sağlayan bir kimlik belgesidir. CI/CD sistemleri, monitoring araçları ve harici servisler de bu mekanizmayı kullanarak cluster kaynaklarına erişir.
Eski Kubernetes sürümlerinde (1.24 öncesi) service account token’ları otomatik olarak oluşturulur ve sona erme tarihi bulunmadan Secret olarak saklanırdı. Bu durum ciddi güvenlik riskleri doğuruyordu çünkü token bir kez ele geçirildiğinde sonsuza kadar geçerliydi.
Kubernetes 1.24 ile birlikte bu yaklaşım değişti. Artık TokenRequest API kullanılarak kısa ömürlü, hedef kitleye özel token’lar oluşturuluyor. Bu yazıda hem eski hem yeni yaklaşımı ele alacağız.
Service Account Oluşturma ve Temel Yapılandırma
Önce temelden başlayalım. CI/CD pipeline’ınız için dedicated bir service account oluşturalım:
# Namespace oluştur
kubectl create namespace cicd-system
# Service account oluştur
kubectl create serviceaccount gitlab-runner
--namespace cicd-system
# Service account'u doğrula
kubectl get serviceaccount gitlab-runner
-n cicd-system
-o yaml
Şimdi bu service account’a gereken minimum yetkiyi verelim. Least privilege prensibi burada hayat kurtarır. Deployment yönetmesi gerekiyorsa sadece o yetkiyi verin:
# Role tanımla
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployment-manager
namespace: production
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "update", "patch"]
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
EOF
# RoleBinding ile service account'u role bağla
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gitlab-runner-deployment-binding
namespace: production
subjects:
- kind: ServiceAccount
name: gitlab-runner
namespace: cicd-system
roleRef:
kind: Role
name: deployment-manager
apiGroup: rbac.authorization.k8s.io
EOF
TokenRequest API ile Kısa Ömürlü Token Üretimi
Kubernetes 1.24+ için TokenRequest API kullanımı önerilen yöntemdir. Bu sayede token’lar belirli bir süre sonra otomatik olarak geçersiz hale gelir:
# 1 saatlik geçerlilik süresiyle token oluştur
kubectl create token gitlab-runner
--namespace cicd-system
--duration=3600s
# Daha spesifik audience tanımlamasıyla token oluştur
kubectl create token gitlab-runner
--namespace cicd-system
--duration=7200s
--audience=https://kubernetes.default.svc
# Token içeriğini decode ederek doğrula
TOKEN=$(kubectl create token gitlab-runner -n cicd-system --duration=3600s)
echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.tool
Token decode ettiğinizde şu alanları kontrol edin:
- exp: Token’ın sona erme zamanı (Unix timestamp)
- iat: Token’ın oluşturulma zamanı
- aud: Token’ın hedef kitlesi (audience)
- sub: Token’ın sahibi olan service account
- kubernetes.io: Kubernetes’e özel metadata
GitLab CI/CD Entegrasyonu
Gerçek dünya senaryosuna geçelim. GitLab CI/CD’nin Kubernetes cluster’ınıza deployment yapabilmesi için güvenli bir entegrasyon kuralım.
Önce GitLab’ın kullanacağı token’ı bir script ile otomatik olarak yönetelim:
#!/bin/bash
# token-refresh.sh - GitLab CI token yenileme scripti
set -euo pipefail
NAMESPACE="cicd-system"
SERVICE_ACCOUNT="gitlab-runner"
TOKEN_DURATION="3600s"
GITLAB_PROJECT_ID="${1:-}"
GITLAB_TOKEN="${GITLAB_CI_TOKEN:-}"
if [[ -z "$GITLAB_PROJECT_ID" || -z "$GITLAB_TOKEN" ]]; then
echo "Kullanim: $0 <project_id>"
echo "GITLAB_CI_TOKEN environment variable ayarlanmali"
exit 1
fi
# Yeni token oluştur
echo "Yeni Kubernetes token oluşturuluyor..."
K8S_TOKEN=$(kubectl create token "$SERVICE_ACCOUNT"
-n "$NAMESPACE"
--duration="$TOKEN_DURATION")
if [[ -z "$K8S_TOKEN" ]]; then
echo "HATA: Token oluşturulamadı"
exit 1
fi
# Token'ın geçerlilik süresini kontrol et
EXPIRY=$(echo "$K8S_TOKEN" | cut -d'.' -f2 |
base64 -d 2>/dev/null |
python3 -c "import sys,json; d=json.load(sys.stdin); print(d['exp'])")
echo "Token sona erme zamanı: $(date -d @$EXPIRY)"
# GitLab CI/CD variable olarak güncelle
curl --silent --fail
--request PUT
--header "PRIVATE-TOKEN: $GITLAB_TOKEN"
--form "value=$K8S_TOKEN"
"https://gitlab.example.com/api/v4/projects/$GITLAB_PROJECT_ID/variables/K8S_SERVICE_TOKEN"
echo "Token başarıyla GitLab'a yüklendi"
GitLab CI pipeline dosyası şu şekilde görünmeli:
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
KUBECTL_VERSION: "1.28.0"
DEPLOY_NAMESPACE: "production"
.kubectl_setup: &kubectl_setup
before_script:
- curl -LO "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
- chmod +x kubectl
- mkdir -p ~/.kube
- |
cat > ~/.kube/config <<KUBECONFIG
apiVersion: v1
kind: Config
clusters:
- cluster:
server: ${K8S_SERVER}
certificate-authority-data: ${K8S_CA_DATA}
name: production-cluster
contexts:
- context:
cluster: production-cluster
user: gitlab-runner
namespace: ${DEPLOY_NAMESPACE}
name: production
current-context: production
users:
- name: gitlab-runner
user:
token: ${K8S_SERVICE_TOKEN}
KUBECONFIG
- ./kubectl auth can-i get deployments -n $DEPLOY_NAMESPACE
deploy_production:
stage: deploy
<<: *kubectl_setup
script:
- ./kubectl set image deployment/myapp
app=${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
-n $DEPLOY_NAMESPACE
- ./kubectl rollout status deployment/myapp
-n $DEPLOY_NAMESPACE
--timeout=300s
environment:
name: production
rules:
- if: $CI_COMMIT_BRANCH == "main"
GitHub Actions Entegrasyonu
GitHub Actions tarafında da benzer bir yapı kuralım. Burada OIDC federation kullanarak token’ları daha güvenli yönetebiliriz:
# GitHub Actions için özel service account
kubectl create serviceaccount github-actions -n cicd-system
# ClusterRole yerine dar kapsamlı Role kullan
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: github-actions-deployer
namespace: staging
rules:
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "update", "patch", "watch"]
- apiGroups: [""]
resources: ["services", "configmaps"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: []
EOF
# Secret tabanlı (eski yöntem - Kubernetes 1.24 öncesi için)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: github-actions-token
namespace: cicd-system
annotations:
kubernetes.io/service-account.name: github-actions
type: kubernetes.io/service-account-token
EOF
GitHub Actions workflow dosyası:
# .github/workflows/deploy.yml
name: Deploy to Kubernetes
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Kubeconfig yapılandır
env:
K8S_SERVER: ${{ secrets.K8S_SERVER }}
K8S_TOKEN: ${{ secrets.K8S_SERVICE_TOKEN }}
K8S_CA: ${{ secrets.K8S_CA_DATA }}
run: |
mkdir -p ~/.kube
cat > ~/.kube/config <<EOF
apiVersion: v1
kind: Config
clusters:
- cluster:
server: ${K8S_SERVER}
certificate-authority-data: ${K8S_CA}
name: k8s-cluster
contexts:
- context:
cluster: k8s-cluster
user: github-actions
name: k8s-context
current-context: k8s-context
users:
- name: github-actions
user:
token: ${K8S_TOKEN}
EOF
- name: Deploy et
run: |
kubectl set image deployment/myapp
app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
-n staging
kubectl rollout status deployment/myapp
-n staging --timeout=5m
Token Rotasyon Otomasyonu
Token rotasyonu manuel yapıldığında unutuluyor ve güvenlik açıkları oluşuyor. Bunu bir CronJob ile otomatize edelim:
# Token rotasyon CronJob'u
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: CronJob
metadata:
name: token-rotator
namespace: cicd-system
spec:
schedule: "0 */6 * * *" # Her 6 saatte bir
jobTemplate:
spec:
template:
spec:
serviceAccountName: token-rotator-sa
restartPolicy: OnFailure
containers:
- name: rotator
image: bitnami/kubectl:1.28
command:
- /bin/bash
- -c
- |
# Mevcut token'ı yenile
NEW_TOKEN=$(kubectl create token gitlab-runner
-n cicd-system
--duration=21600s)
# Secret'ı güncelle
kubectl create secret generic current-cicd-token
--from-literal=token="$NEW_TOKEN"
-n cicd-system
--dry-run=client -o yaml | kubectl apply -f -
echo "Token başarıyla rotate edildi: $(date)"
echo "Sona erme: $(date -d '+6 hours')"
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
EOF
Token Güvenlik Denetimi
Ortamınızdaki mevcut token’ların durumunu düzenli olarak denetlemeniz gerekiyor. Aşağıdaki script, eski ve uzun süreli token’ları tespit eder:
#!/bin/bash
# audit-tokens.sh - Service account token güvenlik denetimi
echo "=== Kubernetes Service Account Token Denetimi ==="
echo "Tarih: $(date)"
echo ""
# Tüm namespace'lerdeki service account'ları listele
echo "## Namespace bazında Service Account'lar:"
for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do
SA_COUNT=$(kubectl get serviceaccounts -n "$ns"
--no-headers 2>/dev/null | wc -l)
if [[ $SA_COUNT -gt 1 ]]; then
echo ""
echo "Namespace: $ns ($SA_COUNT service account)"
kubectl get serviceaccounts -n "$ns"
-o custom-columns='AD:.metadata.name,OLUSTURMA:.metadata.creationTimestamp'
--no-headers
fi
done
echo ""
echo "## Uzun Süreli Secret Token'lar (Sona Erme Tarihi Olmayan):"
kubectl get secrets --all-namespaces
-o jsonpath='{range .items[?(@.type=="kubernetes.io/service-account-token")]}{.metadata.namespace}{"t"}{.metadata.name}{"t"}{.metadata.creationTimestamp}{"n"}{end}' |
awk -F't' '{
cmd = "date -d " $3 " +%s"
cmd | getline ts
close(cmd)
age_days = (systime() - ts) / 86400
if (age_days > 30) {
printf "UYARI - %s/%s (%.0f gun once olusturuldu)n", $1, $2, age_days
}
}'
echo ""
echo "## ClusterAdmin Yetkili Service Account'lar:"
kubectl get clusterrolebindings -o json |
python3 -c "
import sys, json
data = json.load(sys.stdin)
for item in data['items']:
if item.get('roleRef', {}).get('name') == 'cluster-admin':
for subj in item.get('subjects', []):
if subj.get('kind') == 'ServiceAccount':
print(f'KRITIK: {subj["namespace"]}/{subj["name"]} - cluster-admin yetkisi var!')
"
Vault Entegrasyonu ile Merkezi Token Yönetimi
Büyük ortamlarda HashiCorp Vault ile merkezi token yönetimi kurmak çok daha sürdürülebilir:
# Vault Kubernetes auth backend yapılandırması
# Bu komutlar Vault sunucusunda çalıştırılır
# Kubernetes auth method'u etkinleştir
vault auth enable kubernetes
# Kubernetes API bilgilerini yapılandır
K8S_HOST=$(kubectl config view --raw
-o jsonpath='{.clusters[0].cluster.server}')
K8S_CA=$(kubectl get configmap kube-root-ca.crt
-n kube-system
-o jsonpath='{.data.ca.crt}' | base64 -w0)
vault write auth/kubernetes/config
kubernetes_host="$K8S_HOST"
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# CI/CD sistemleri için Vault role tanımla
vault write auth/kubernetes/role/cicd-deployer
bound_service_account_names="gitlab-runner,github-actions"
bound_service_account_namespaces="cicd-system"
policies="cicd-deployment-policy"
ttl=1h
max_ttl=4h
# Policy tanımla
cat <<POLICY | vault policy write cicd-deployment-policy -
path "secret/data/cicd/*" {
capabilities = ["read", "list"]
}
path "kubernetes/creds/deployment-role" {
capabilities = ["create", "update"]
}
POLICY
# Kubernetes token ile Vault'a login ol
VAULT_TOKEN=$(curl -s
--request POST
--data "{"jwt": "$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)", "role": "cicd-deployer"}"
https://vault.example.com/v1/auth/kubernetes/login |
python3 -c "import sys,json; print(json.load(sys.stdin)['auth']['client_token'])")
echo "Vault token alındı: ${VAULT_TOKEN:0:20}..."
Monitoring ve Alerting
Token sorunlarını proaktif olarak tespit etmek için Prometheus alert kuralları tanımlayın:
# PrometheusRule - token güvenlik alertleri
cat <<EOF | kubectl apply -f -
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: service-account-token-alerts
namespace: monitoring
spec:
groups:
- name: token-security
interval: 5m
rules:
- alert: LongLivedServiceAccountToken
expr: |
(time() - kube_secret_created{type="kubernetes.io/service-account-token"})
> (30 * 24 * 3600)
for: 1h
labels:
severity: warning
annotations:
summary: "30 günden eski service account token tespit edildi"
description: "{{ $labels.namespace }}/{{ $labels.secret }} token'ı {{ $value | humanizeDuration }} süredir aktif"
- alert: ClusterAdminServiceAccount
expr: |
kube_clusterrolebinding_info{role_name="cluster-admin"}
* on(name) group_left(subject_name, subject_namespace)
kube_clusterrolebinding_subject_info{subject_kind="ServiceAccount"}
for: 5m
labels:
severity: critical
annotations:
summary: "Service account cluster-admin yetkisine sahip"
description: "{{ $labels.subject_namespace }}/{{ $labels.subject_name }} kritik yetkiye sahip"
EOF
Sık Yapılan Hatalar ve Kaçınma Yöntemleri
Production ortamlarında en çok karşılaştığım sorunlar ve çözümleri:
- Sonsuz süreli token kullanımı: Her zaman
--durationparametresi ile süre belirleyin. İdeal süre 1-8 saat arasındadır.
- Default service account kullanımı: Pod manifest dosyalarınızda
automountServiceAccountToken: falseekleyin ve özel service account oluşturun.
- Token’ları Git repo’suna commit etmek:
.gitignoredosyanızakubeconfig,*.tokenvekube.configekleyin. Pre-commit hook ile kontrol edin.
- Geniş kapsamlı RBAC yetkileri: ClusterRole yerine namespace bazlı Role kullanın. Wildcard (
*) kullanmaktan kaçının.
- Token rotasyonunu manuel yapmak: Yukarıdaki CronJob örneği gibi otomasyona alın.
- Audit log’ları izlememek:
kubectl get eventsve API server audit log’larını düzenli kontrol edin.
- Tek bir token ile tüm ortamları yönetmek: Her environment (staging, production) için ayrı service account ve token kullanın.
Sonuç
Service account token yönetimi, Kubernetes güvenliğinin bel kemiğini oluşturuyor. Kısa yaşam süreli token’lar, minimum yetki prensibi ve otomatik rotasyon mekanizmaları bir arada uygulandığında güvenlik açığı yüzeyi ciddi ölçüde daralıyor.
Bu yazıda anlattıklarımı maddeler halinde özetleyecek olursam:
- TokenRequest API kullanarak süreli token oluşturun
- Her CI/CD sistemi için ayrı service account tanımlayın
- RBAC kurallarını mümkün olduğunca dar tutun
- Token rotasyonunu CronJob ile otomatize edin
- Vault gibi merkezi bir secret yönetim sistemi düşünün
- Audit scriptleri ile düzenli güvenlik taraması yapın
- Prometheus alert’leri ile anormalilikleri proaktif izleyin
Küçük bir cluster’da bile bu pratikleri hayata geçirmek bir gün büyük bir güvenlik ihlalinin önüne geçebilir. Token yönetimini ciddiye alın, gelecekteki siz teşekkür edecektir.
