Create a Long-Lived Kubeconfig with Custom Permissions
1. Overview
What is this feature?
A long-lived kubeconfig lets you build your own kubeconfig file with a lifetime and permissions you control, instead of relying on the kubeconfig from FPT Portal, which is valid for 24 hours only.
The problem it solves
The kubeconfig you download from FPT Portal expires after 24 hours. When you use it for a continuously running third-party system — ArgoCD, Rancher, Jenkins, Vault, monitoring — the connection breaks and you have to download and update the file again by hand.
What you get
After following this guide you have a kubeconfig that works without interruption, with permissions scoped to each system or each member of your team.
2. Who Should Use This
| Role | When you need it |
|---|---|
| DevOps / Platform Engineer | Connect ArgoCD, Rancher, Jenkins, Vault, monitoring, backup, or automation scripts to an M-FKE cluster |
| Developer | You need a personal, long-lived kubeconfig for a development environment |
| Team Lead | You issue kubeconfigs with the right permissions to each member of your team |
3. Key Capabilities
| Capability | Description |
|---|---|
| Custom lifetime | A token that never expires, or one with the lifetime you pick (90 days, 1 year) |
| Custom permissions (RBAC) | Full cluster access, read-only, scoped to namespaces, or fine-grained down to individual resource types |
| Two authentication methods | ServiceAccount with a bearer token (machine-to-machine) or a client certificate over mTLS (per person) |
| Self-service and revocable | Create, track, and revoke credentials yourself, with no support request |
| Works across platforms | ArgoCD, Rancher, Jenkins, Vault, Prometheus, and anything else that accepts a kubeconfig |
4. How to Use — Step-by-Step
Pick the right option
| Option 1: ServiceAccount + Token | Option 2: Client Certificate | |
|---|---|---|
| Authentication | Bearer token | Certificate (mTLS) |
| Lifetime | Never expires, or your choice | Your choice, up to 30 days |
| Best for | Machine-to-machine connections (ArgoCD, Rancher, Jenkins, Vault) | A personal kubeconfig per team member |
| Recommendation | ✅ Use this one for most cases | When you need per-person identity by User or Group |
Prerequisites
Before you start, make sure:
-
kubectlis installed on your machine - You downloaded the cluster kubeconfig from FPT Portal and it is still within its 24-hour window
- Your machine can reach the API server — add your IP to the allowlist if the cluster restricts access
Note: The commands use a Linux or macOS shell. On Windows, run them in WSL or Git Bash.
Shared preparation (required for both options)
Step P-1: Connect to the cluster with the Portal kubeconfig
Action: Export the Portal kubeconfig and check the connection:
export KUBECONFIG=/path/to/kubeconfig-portal.yaml
kubectl get nodes
Expected result:
NAME STATUS ROLES AGE VERSION
fke-node-abc Ready <none> 10d v1.28.x
fke-node-def Ready <none> 10d v1.28.x
⚠️ If the command fails, check the path to the kubeconfig file and how long ago you downloaded it — it is valid for 24 hours.
Step P-2: Read the cluster details (SERVER and CA)
Action: Read the API server address and the CA certificate:
SERVER=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.server}')
CA_B64=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
echo "$SERVER"
Expected result:
https://api.fke-xxxxxxxx.fptcloud.com:443
Important:
SERVERandCA_B64belong to the cluster, so they do not expire with the Portal kubeconfig. You reuse them in the steps below.
Use case 1: Kubeconfig with a ServiceAccount and token (recommended)
Use this for ArgoCD, Rancher, Jenkins, Vault, monitoring, backup, and any machine-to-machine connection.
Step 1.1: Create a ServiceAccount
Action: Create a new ServiceAccount. Create one per system:
NAMESPACE=kube-system # or a dedicated namespace, for example: external-access
SA_NAME=argocd-deployer # name it after the system that uses it
kubectl create serviceaccount "$SA_NAME" -n "$NAMESPACE"
Expected result:
serviceaccount/argocd-deployer created
Tip: Use a name that identifies the system:
argocd-deployer,jenkins-ci,rancher-agent,monitoring-reader.
Step 1.2: Grant permissions (RBAC)
Action: Pick one permission template that matches what you need.
Template A — Full cluster access (equivalent to the Portal kubeconfig):
kubectl create clusterrolebinding "${SA_NAME}-admin" \
--clusterrole=cluster-admin \
--serviceaccount="${NAMESPACE}:${SA_NAME}"
Template B — Read-only across the cluster (monitoring, dashboards):
kubectl create clusterrolebinding "${SA_NAME}-view" \
--clusterrole=view \
--serviceaccount="${NAMESPACE}:${SA_NAME}"
Template C — Full access within specific namespaces (CI/CD):
for ns in app-dev app-staging; do
kubectl create rolebinding "${SA_NAME}-edit" -n "$ns" \
--clusterrole=edit \
--serviceaccount="${NAMESPACE}:${SA_NAME}"
done
Template D — Fine-grained custom permissions — for example, deploy workloads but never read or modify Secrets.
Create custom-deployer-rbac.yaml:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: custom-deployer
rules:
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
verbs: ["*"]
- apiGroups: [""]
resources: ["services", "configmaps", "pods", "pods/log"]
verbs: ["*"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-deployer-custom
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: custom-deployer
subjects:
- kind: ServiceAccount
name: argocd-deployer
namespace: kube-system
Apply it:
kubectl apply -f custom-deployer-rbac.yaml
Tip: Kubernetes ships with these ClusterRoles:
cluster-admin(full access),admin,edit(read and write, but not RBAC), andview(read-only). List them withkubectl get clusterroles.
Step 1.3: Get a token
Pick one of the two options, depending on what you need.
Option A — A token that never expires (recommended for automated systems):
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: ${SA_NAME}-token
namespace: ${NAMESPACE}
annotations:
kubernetes.io/service-account.name: ${SA_NAME}
type: kubernetes.io/service-account-token
EOF
# Wait a few seconds for the token to be populated, then read it
TOKEN=$(kubectl get secret "${SA_NAME}-token" -n "$NAMESPACE" \
-o jsonpath='{.data.token}' | base64 -d)
The token is valid indefinitely. It stops working only when you delete the Secret or the ServiceAccount.
Option B — A token with the lifetime you choose (safer, but you must renew it):
# For example, 90 days = 2160h
TOKEN=$(kubectl create token "$SA_NAME" -n "$NAMESPACE" --duration=2160h)
The token expires on its own. When it does, create a new one and update the system that uses it.
Step 1.4: Build the new kubeconfig
Action:
CLUSTER_NAME=my-fke-cluster # any name you like
cat > kubeconfig-${SA_NAME}.yaml <<EOF
apiVersion: v1
kind: Config
clusters:
- name: ${CLUSTER_NAME}
cluster:
certificate-authority-data: ${CA_B64}
server: ${SERVER}
contexts:
- name: ${SA_NAME}@${CLUSTER_NAME}
context:
cluster: ${CLUSTER_NAME}
user: ${SA_NAME}
current-context: ${SA_NAME}@${CLUSTER_NAME}
users:
- name: ${SA_NAME}
user:
token: ${TOKEN}
EOF
Result: kubeconfig-argocd-deployer.yaml is created in the current directory.
Step 1.5: Verify the new kubeconfig
Action:
export KUBECONFIG=$PWD/kubeconfig-${SA_NAME}.yaml
# Confirm the identity
kubectl auth whoami
# Check the connection
kubectl get nodes
# Check a specific permission
kubectl auth can-i create deployments -n app-dev
Expected result:
# kubectl auth whoami
ATTRIBUTE VALUE
Username system:serviceaccount:kube-system:argocd-deployer
Groups [system:serviceaccounts system:serviceaccounts:kube-system system:authenticated]
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
fke-node-abc Ready <none> 10d v1.28.x
# kubectl auth can-i create deployments -n app-dev
yes
Result
✅ kubeconfig-argocd-deployer.yaml is ready to import into ArgoCD, Rancher, Jenkins, or Vault. The connection survives past the 24-hour mark.
Use case 2: Personal kubeconfig with a client certificate
Use this to issue a kubeconfig per team member, identified by User and Group.
⚠️ Limit: The certificate lasts 30 days at most. For a long-lived connection from an automated system, use use case 1.
Step 2.1: Create a private key and a CSR
Action:
USER_NAME=dev-nguyenvana
GROUP=dev-team # used to grant permissions per group
# Create the private key
openssl genrsa -out ${USER_NAME}.key 2048
# Create the Certificate Signing Request
openssl req -new -key ${USER_NAME}.key -out ${USER_NAME}.csr \
-subj "/CN=${USER_NAME}/O=${GROUP}"
Expected result:
Generating RSA private key, 2048 bit long modulus
...
What these mean:
CN(Common Name) is the user name andO(Organization) is the group name. Kubernetes uses both to decide permissions.
Step 2.2: Submit the CSR to the cluster
Action:
kubectl apply -f - <<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: ${USER_NAME}
spec:
request: $(base64 -w 0 < ${USER_NAME}.csr)
signerName: kubernetes.io/kube-apiserver-client
expirationSeconds: 2592000 # 30 days = 2592000 seconds
usages:
- client auth
EOF
Expected result:
certificatesigningrequest.certificates.k8s.io/dev-nguyenvana created
Step 2.3: Approve the CSR and collect the certificate
Action:
# Approve
kubectl certificate approve ${USER_NAME}
# Read the signed certificate
kubectl get csr ${USER_NAME} -o jsonpath='{.status.certificate}' | base64 -d > ${USER_NAME}.crt
# Check the actual expiry
openssl x509 -in ${USER_NAME}.crt -noout -enddate
Expected result:
certificatesigningrequest.certificates.k8s.io/dev-nguyenvana approved
notAfter=Oct 3 07:30:00 2026 GMT
Step 2.4: Grant permissions to the User or Group
Action:
kubectl create clusterrolebinding ${GROUP}-edit \
--clusterrole=edit \
--group=${GROUP}
Permissions are bound to the Group (
O=dev-teamin the certificate), so every member of that Group shares them. You do not need to grant them again when someone joins.
Step 2.5: Build the kubeconfig
Action:
cat > kubeconfig-${USER_NAME}.yaml <<EOF
apiVersion: v1
kind: Config
clusters:
- name: my-fke-cluster
cluster:
certificate-authority-data: ${CA_B64}
server: ${SERVER}
contexts:
- name: ${USER_NAME}@my-fke-cluster
context:
cluster: my-fke-cluster
user: ${USER_NAME}
current-context: ${USER_NAME}@my-fke-cluster
users:
- name: ${USER_NAME}
user:
client-certificate-data: $(base64 -w 0 < ${USER_NAME}.crt)
client-key-data: $(base64 -w 0 < ${USER_NAME}.key)
EOF
Result
✅ kubeconfig-dev-nguyenvana.yaml is ready to hand to the team member. It is valid for 30 days.
Renewal
When the certificate is close to expiring, repeat steps 2.1 to 2.3 and 2.5. The permissions stay as they are, because CN and O do not change. You can renew with the old kubeconfig while it is still valid, as long as the user has permissions on certificatesigningrequests.
Use case 3: Connect the kubeconfig to third-party systems
Once you have a kubeconfig or token from use case 1, connect it to the systems below.
ArgoCD
Option 1 — CLI:
argocd cluster add argocd-deployer@my-fke-cluster \
--kubeconfig kubeconfig-argocd-deployer.yaml
Option 2 — Declarative (cluster Secret): Set bearerToken to your TOKEN and caData to your CA_B64 in the Secret manifest.
Rancher (import cluster)
Action: Run the Rancher import command with the new kubeconfig.
⚠️ Installing the Rancher agent needs
cluster-admin— template A in step 1.2.
Jenkins
Action:
- Go to Jenkins → Manage Jenkins → Credentials.
- Add a credential of type Secret file and upload
kubeconfig-argocd-deployer.yaml. - In the pipeline, use it through
withKubeConfigor theKUBECONFIGenvironment variable.
Vault (Kubernetes auth method)
Configuration:
| Parameter | Value |
|---|---|
kubernetes_host | Your SERVER value |
kubernetes_ca_cert | The CA contents, decoded from CA_B64 |
token_reviewer_jwt | The token of a ServiceAccount bound to the system:auth-delegator ClusterRole |
5. Interface Explanation — Key commands and parameters
| Command / parameter | Description | Example |
|---|---|---|
kubectl create serviceaccount | Create a service account in the cluster | kubectl create sa argocd-deployer -n kube-system |
kubectl create clusterrolebinding | Grant permissions across the cluster | --clusterrole=cluster-admin |
kubectl create rolebinding | Grant permissions within one namespace | -n app-dev --clusterrole=edit |
kubectl create token --duration | Create a token with a set lifetime | --duration=2160h (90 days) |
kubectl auth whoami | Confirm the identity in use | Returns the ServiceAccount or User name |
kubectl auth can-i | Check a specific permission | kubectl auth can-i create deployments |
kubectl certificate approve | Approve a CSR (option 2) | kubectl certificate approve dev-nguyenvana |
kubectl delete secret | Revoke a token by deleting its Secret | kubectl delete secret argocd-deployer-token |
--clusterrole=cluster-admin | Full cluster access | Equivalent to the Portal kubeconfig |
--clusterrole=view | Read-only across the cluster | For monitoring and dashboards |
--clusterrole=edit | Read and write, but not RBAC | For CI/CD and developers |
6. System States
Success
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
fke-node-abc Ready <none> 10d v1.28.x
→ The kubeconfig works and the connection is stable.
Error: Unauthorized (401)
error: You must be logged in to the server (Unauthorized)
→ The token is invalid or has been revoked. See Common Issues & Solutions.
Error: Forbidden (403)
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:kube-system:argocd-deployer"
cannot list resource "pods" in API group "" in the namespace "default"
→ The ServiceAccount lacks RBAC permissions for that resource or namespace. Grant more permissions.
Error: certificate error
Unable to connect to the server: x509: certificate signed by unknown authority
→ certificate-authority-data is wrong or missing. Read it again from the Portal kubeconfig.
Token created successfully
$ kubectl get secret argocd-deployer-token -n kube-system
NAME TYPE DATA AGE
argocd-deployer-token kubernetes.io/service-account-token 3 5s
→ The Secret exists and the token is populated. It is ready to use.
7. Common Issues & Solutions
| Issue | Cause | Solution |
|---|---|---|
| Unauthorized (401) with a token you just created | The Secret is not populated yet, or the token was copied with a missing or extra line break | Wait 5 to 10 seconds and read the token again. Check for stray characters when you copy it |
| Forbidden (403) on a resource | The ServiceAccount lacks RBAC permissions for that resource or namespace | Run kubectl auth can-i --list --as=system:serviceaccount:<ns>:<sa-name>, then grant what is missing |
| x509: certificate signed by unknown authority | certificate-authority-data is wrong or missing in the kubeconfig | Read CA_B64 again from the Portal kubeconfig, per step P-2 |
| CSR is Approved but there is no certificate | The cluster has not finished signing it | Wait a few seconds. If it takes longer, run kubectl get csr <name> -o yaml and confirm signerName is kubernetes.io/kube-apiserver-client |
| The certificate is issued for less time than you asked for | The cluster caps it at 30 days | Expected — this is a cluster limit. Use option 1 if you need longer |
The token from kubectl create token is shorter than requested | The cluster caps the lifetime for this token type | Switch to option A, a non-expiring token through a Secret |
| The kubeconfig stops working after maintenance | The cluster rotated its certificates | Check CA_B64 and the token, and recreate them if needed |
8. Tips & Best Practices
Security
| Do this | Details |
|---|---|
| Keep tokens in a secret store | Vault, Jenkins Credentials, GitLab CI/CD variables — never plain text |
| Never commit them | Add kubeconfig-*.yaml to .gitignore |
| Revoke as soon as you suspect a leak | kubectl delete secret ${SA_NAME}-token -n ${NAMESPACE} — the token stops working immediately |
| Never send them over unencrypted channels | No Slack, plain-text email, or chat |
Management
| Do this | Details |
|---|---|
| One ServiceAccount per system | Do not share. Revoking one then leaves the others untouched |
| Label every credential | kubectl label sa "$SA_NAME" -n "$NAMESPACE" managed-by=customer purpose=argocd |
| Grant the least permission that works | Start with view or edit. Use cluster-admin only when you truly need it |
| Track your own credentials | Credentials you create yourself do not appear in the Portal — tracking them is on you |
| List what you created | kubectl get sa -l managed-by=customer --all-namespaces |
Recommended workflow
| Step | Action |
|---|---|
| 1 | Create the ServiceAccount and label it |
| 2 | Grant the least permission that works |
| 3 | Get a token, preferably one with a lifetime |
| 4 | Build the kubeconfig and check it with kubectl auth can-i |
| 5 | Import it into the target system |
| 6 | Set a reminder to renew, if the token expires |
9. FAQ
Does downloading a new kubeconfig from the Portal affect the one I built?
→ No. The Portal kubeconfig and the credentials you create are completely independent and work side by side.
Does my kubeconfig survive a Kubernetes upgrade?
→ Yes. An upgrade does not remove ServiceAccounts, tokens, or the RBAC you created.
How many ServiceAccounts can I create?
→ There is no practical limit. Create them deliberately — one per system — and revoke the ones you no longer use.
How do I find out what a token is allowed to do?
→ Run kubectl auth can-i --list with the kubeconfig that carries the token.
What happens when the cluster rotates its certificates?
→ certificate-authority-data and the token may need updating. When FPT Cloud sends a maintenance notice, check the kubeconfigs you built.
What happens if the cluster is deleted and recreated?
→ Every credential you created goes with the cluster. Run the process again on the new one.
Can I renew a certificate (option 2) without the Portal kubeconfig?
→ Yes, if the user has permissions on certificatesigningrequests. Use the old kubeconfig while it is still valid.
This document belongs to FPT Managed Kubernetes Engine (M-FKE) — FPT Cloud.
If you run into trouble, contact the FPT Cloud support team.