← Back to Blog

How to Share kubeconfig With a Contractor Without Handing Over Your Cluster

A contractor starts Monday to help you migrate a stuck Helm release. The fastest way to onboard them is to email your ~/.kube/config. Congratulations: you just gave a short-term contractor your full cluster-admin session, with no expiration, and a copy that lives forever in their mailbox. Here is what to do instead.

Why Your Default kubeconfig Is Too Powerful

The kubeconfig you use daily probably:

  • Authenticates as a user with broad RBAC (often cluster-admin)
  • Uses a long-lived client certificate or OIDC token
  • Has no expiration by default
  • Contains multiple contexts (dev, staging, prod) — not just the one they need
  • May embed credentials for cloud auth plugins (GKE, EKS, AKS)

Sharing it wholesale is a catastrophic default. The right approach: create a dedicated ServiceAccount, scope its RBAC to exactly what the contractor needs, issue a short-lived token, and ship a minimal kubeconfig file.

The 5-Step Secure Handoff

Step 1: Create a Dedicated ServiceAccount and Namespace

Never share your user credentials. Give every contractor their own ServiceAccount. If the work is namespace-scoped, create a dedicated namespace too.

kubectl create namespace contractor-migration
kubectl -n contractor-migration create serviceaccount alice-contractor

Step 2: Define Minimal RBAC

Work backward from the task. "Migrate a Helm release" might need: read pods/deployments/services in the namespace, read ConfigMaps, patch deployments. It does not need: anything in kube-system, any cluster-wide permissions, any secrets outside the namespace.

# contractor-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: migration-role
  namespace: contractor-migration
rules:
  - apiGroups: [""]
    resources: ["pods", "services", "configmaps", "events"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets", "statefulsets"]
    verbs: ["get", "list", "watch", "patch", "update"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: alice-contractor-binding
  namespace: contractor-migration
subjects:
  - kind: ServiceAccount
    name: alice-contractor
    namespace: contractor-migration
roleRef:
  kind: Role
  name: migration-role
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f contractor-role.yaml

Notice: no access to Secrets. Almost nobody actually needs direct Secret read access for application debugging. If they say they do, ask why — usually there is a safer alternative (env var reference, Secret name only, or vault integration).

Step 3: Issue a Short-Lived Token

Modern Kubernetes (1.24+) removes the default long-lived token Secret for ServiceAccounts. Use kubectl create token to generate a time-bound JWT:

TOKEN=$(kubectl -n contractor-migration create token alice-contractor --duration=24h)

This token auto-expires in 24 hours. No revocation ceremony required — just do not reissue.

For longer engagements, reissue daily or weekly with the duration set to the next check-in window.

Step 4: Build the Minimal kubeconfig

Construct a kubeconfig with only what the contractor needs: cluster CA, API endpoint, the scoped token, and the namespace as a default context.

# Extract cluster CA and endpoint from your current config
CLUSTER_NAME=$(kubectl config view --minify -o jsonpath='{.clusters[0].name}')
CLUSTER_SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
CLUSTER_CA=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')

cat > contractor-kubeconfig.yaml <<EOF
apiVersion: v1
kind: Config
clusters:
  - name: ${CLUSTER_NAME}
    cluster:
      server: ${CLUSTER_SERVER}
      certificate-authority-data: ${CLUSTER_CA}
contexts:
  - name: contractor-context
    context:
      cluster: ${CLUSTER_NAME}
      namespace: contractor-migration
      user: alice-contractor
users:
  - name: alice-contractor
    user:
      token: ${TOKEN}
current-context: contractor-context
EOF

Verify it works before handing it over:

KUBECONFIG=./contractor-kubeconfig.yaml kubectl get pods
# Should succeed. Now try something outside scope:
KUBECONFIG=./contractor-kubeconfig.yaml kubectl get secrets
# Should fail with "Forbidden" — confirming RBAC is scoped correctly.

Step 5: Hand It Over Without Email

The kubeconfig file contains a live API credential. Treat it like a password.

  • Do not email it.
  • Do not upload to Slack as an attachment.
  • Do not paste the contents into a ticket.

Use a zero-knowledge encrypted paste with burn-after-read:

  1. Upload the kubeconfig file contents.
  2. Set TTL to match the token lifetime (24h).
  3. Enable burn-after-read.
  4. Send the URL to the contractor. The token inside is useless without the URL, and the URL becomes useless after first download or TTL expiry.

Hand Over Kubernetes Access Without Emailing Tokens

SecureBin sends kubeconfig files as encrypted, expiring URLs. Burn-after-read ensures the credential is retrieved once and gone. Audit log shows exactly when the contractor opened it.

Create Encrypted Paste

One-Command Revocation

When the engagement ends:

kubectl delete serviceaccount alice-contractor -n contractor-migration
kubectl delete rolebinding alice-contractor-binding -n contractor-migration

The token is immediately unusable. If the contractor was doing long-running tasks (port-forwards, watches), those sessions die instantly.

If the whole engagement namespace was dedicated, cleaner still:

kubectl delete namespace contractor-migration

One command. Everything created in that namespace is gone. No hunt-and-peck.

Audit Trail: Who Used the Token

Kubernetes API server audit logs (if enabled) show every request the contractor made. Enable audit logging at least at Metadata level so you have forensic visibility:

# AuditPolicy snippet
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: Metadata
    userGroups: ["system:serviceaccounts:contractor-migration"]

For contractors working on sensitive systems, raise to Request or RequestResponse for full bodies.

When RBAC Is Not Enough: Cluster-Level Read

Some contractor tasks legitimately need cluster-scoped reads (diagnosing CNI issues, node problems). Grant those surgically:

kubectl create clusterrole contractor-cluster-read \
  --verb=get,list,watch \
  --resource=nodes,namespaces,persistentvolumes

kubectl create clusterrolebinding alice-contractor-cluster-read \
  --clusterrole=contractor-cluster-read \
  --serviceaccount=contractor-migration:alice-contractor

Still no write, still no secrets. Scope up only what the task demands, not what feels convenient.

For Managed Kubernetes (EKS, GKE, AKS)

The ServiceAccount approach works everywhere, but managed providers have additional patterns worth knowing.

EKS: IAM Roles for Service Accounts (IRSA)

Bind the contractor SA to an IAM role with scoped AWS permissions. The pod (or contractor's kubectl sessions, via aws eks update-kubeconfig) authenticates through AWS IAM, and you can revoke by removing the role annotation.

GKE: Workload Identity

Similar concept. The contractor's ServiceAccount maps to a GCP service account, and you revoke by removing the IAM binding.

AKS: Azure AD Integration

For human contractors, use Azure AD authentication with a dedicated group. Revocation is a group removal in AAD.

In all three cases, you still want the Kubernetes RBAC scoping described above. Cloud IAM alone is too coarse.

Common Mistakes

1. Sharing your personal kubeconfig. Your context list leaks your whole cluster inventory. Always build a fresh one.

2. Using cluster-admin "just to make it work". Take the extra 10 minutes to scope. Nine times out of ten you will discover the task needs far less than you thought.

3. Using a long-lived static token. Kubernetes 1.24+ moved away from these for a reason. Always use kubectl create token --duration=....

4. Forgetting to revoke. Add a calendar reminder for end-of-engagement, or better, expire the SA automatically via a TTL controller.

5. Emailing the kubeconfig. Every email system keeps a copy forever. The token may expire, but so much metadata about your cluster (CA, endpoint, namespaces) is in that file.

6. Pasting the kubeconfig in a ticket. Tickets are searchable forever by anyone with read access, including future hires and external auditors.

Troubleshooting Checklist

  • Did you create a dedicated ServiceAccount (not reuse your own user)?
  • Is the RBAC scoped to the minimum verbs on the minimum resources?
  • Did you test the kubeconfig to confirm both allowed and denied operations?
  • Is the token duration shorter than the engagement?
  • Is audit logging enabled for this ServiceAccount?
  • Is the handoff via encrypted burn-after-read URL, not email?
  • Is there a revocation reminder set for engagement end?

Frequently Asked Questions

What is the difference between a user and a ServiceAccount in Kubernetes?

Users are managed outside the cluster (OIDC, cloud IAM, x509 certs). ServiceAccounts are Kubernetes-native objects, scoped to a namespace. For contractors, ServiceAccounts give you tighter RBAC and a single-command revocation path.

Can I share my kubeconfig via S3 or a shared drive?

Not without additional controls. An S3 presigned URL without TTL is no better than email. If you use S3, set a very short URL expiry, enable access logging, and delete the object immediately after retrieval.

How do I handle contractors who need kubectl exec access?

Add pods/exec to the Role verbs. Audit every exec event. Consider whether the same task could be accomplished with kubectl debug instead — ephemeral containers are easier to audit.

What about contractors using third-party tools (Lens, OpenLens, k9s)?

These all accept a kubeconfig file. The scoped kubeconfig approach works identically. Do not give the contractor your Lens workspace — have them set up their own pointed at your scoped kubeconfig.

Should I rotate the kubeconfig during a long engagement?

Yes. Default to weekly token rotation, or daily if the work involves sensitive data. Our API key rotation guide covers the general principles.

What happens if the contractor's laptop is compromised?

If you used a short-lived token, damage is bounded to what they can do before it expires. Rotate immediately (delete and recreate the SA), and review audit logs for anomalous activity during the window. This is precisely why scoped + short-lived beats your personal cluster-admin kubeconfig.

Key Takeaways

  • Never share your personal kubeconfig. Build a scoped one per contractor.
  • Dedicated ServiceAccount + namespace-scoped Role + RoleBinding is the pattern.
  • Use kubectl create token --duration=24h for time-bound access — no long-lived static tokens.
  • Hand over via encrypted, burn-after-read URL. Never email.
  • Revoke with a single kubectl delete at engagement end.
  • Enable audit logging for the ServiceAccount so you have a record of every action.

Related reading: Debug Kubernetes Pods Without Exposing Secrets, Kubernetes Secrets Management, Kubernetes Security Best Practices, API Key Rotation Best Practices, Credential Sharing Policy Template.

UK
Written by Usman Khan
DevOps Engineer | MSc Cybersecurity | CEH | AWS Solutions Architect

Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.