Skip to main content

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

RoleWhen you need it
DevOps / Platform EngineerConnect ArgoCD, Rancher, Jenkins, Vault, monitoring, backup, or automation scripts to an M-FKE cluster
DeveloperYou need a personal, long-lived kubeconfig for a development environment
Team LeadYou issue kubeconfigs with the right permissions to each member of your team

3. Key Capabilities

CapabilityDescription
Custom lifetimeA 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 methodsServiceAccount with a bearer token (machine-to-machine) or a client certificate over mTLS (per person)
Self-service and revocableCreate, track, and revoke credentials yourself, with no support request
Works across platformsArgoCD, 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 + TokenOption 2: Client Certificate
AuthenticationBearer tokenCertificate (mTLS)
LifetimeNever expires, or your choiceYour choice, up to 30 days
Best forMachine-to-machine connections (ArgoCD, Rancher, Jenkins, Vault)A personal kubeconfig per team member
RecommendationUse this one for most casesWhen you need per-person identity by User or Group

Prerequisites

Before you start, make sure:

  • kubectl is 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: SERVER and CA_B64 belong to the cluster, so they do not expire with the Portal kubeconfig. You reuse them in the steps below.


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), and view (read-only). List them with kubectl 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 and O (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-team in 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:

  1. Go to JenkinsManage JenkinsCredentials.
  2. Add a credential of type Secret file and upload kubeconfig-argocd-deployer.yaml.
  3. In the pipeline, use it through withKubeConfig or the KUBECONFIG environment variable.

Vault (Kubernetes auth method)

Configuration:

ParameterValue
kubernetes_hostYour SERVER value
kubernetes_ca_certThe CA contents, decoded from CA_B64
token_reviewer_jwtThe token of a ServiceAccount bound to the system:auth-delegator ClusterRole

5. Interface Explanation — Key commands and parameters

Command / parameterDescriptionExample
kubectl create serviceaccountCreate a service account in the clusterkubectl create sa argocd-deployer -n kube-system
kubectl create clusterrolebindingGrant permissions across the cluster--clusterrole=cluster-admin
kubectl create rolebindingGrant permissions within one namespace-n app-dev --clusterrole=edit
kubectl create token --durationCreate a token with a set lifetime--duration=2160h (90 days)
kubectl auth whoamiConfirm the identity in useReturns the ServiceAccount or User name
kubectl auth can-iCheck a specific permissionkubectl auth can-i create deployments
kubectl certificate approveApprove a CSR (option 2)kubectl certificate approve dev-nguyenvana
kubectl delete secretRevoke a token by deleting its Secretkubectl delete secret argocd-deployer-token
--clusterrole=cluster-adminFull cluster accessEquivalent to the Portal kubeconfig
--clusterrole=viewRead-only across the clusterFor monitoring and dashboards
--clusterrole=editRead and write, but not RBACFor 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

IssueCauseSolution
Unauthorized (401) with a token you just createdThe Secret is not populated yet, or the token was copied with a missing or extra line breakWait 5 to 10 seconds and read the token again. Check for stray characters when you copy it
Forbidden (403) on a resourceThe ServiceAccount lacks RBAC permissions for that resource or namespaceRun kubectl auth can-i --list --as=system:serviceaccount:<ns>:<sa-name>, then grant what is missing
x509: certificate signed by unknown authoritycertificate-authority-data is wrong or missing in the kubeconfigRead CA_B64 again from the Portal kubeconfig, per step P-2
CSR is Approved but there is no certificateThe cluster has not finished signing itWait 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 forThe cluster caps it at 30 daysExpected — this is a cluster limit. Use option 1 if you need longer
The token from kubectl create token is shorter than requestedThe cluster caps the lifetime for this token typeSwitch to option A, a non-expiring token through a Secret
The kubeconfig stops working after maintenanceThe cluster rotated its certificatesCheck CA_B64 and the token, and recreate them if needed

8. Tips & Best Practices

Security

Do thisDetails
Keep tokens in a secret storeVault, Jenkins Credentials, GitLab CI/CD variables — never plain text
Never commit themAdd kubeconfig-*.yaml to .gitignore
Revoke as soon as you suspect a leakkubectl delete secret ${SA_NAME}-token -n ${NAMESPACE} — the token stops working immediately
Never send them over unencrypted channelsNo Slack, plain-text email, or chat

Management

Do thisDetails
One ServiceAccount per systemDo not share. Revoking one then leaves the others untouched
Label every credentialkubectl label sa "$SA_NAME" -n "$NAMESPACE" managed-by=customer purpose=argocd
Grant the least permission that worksStart with view or edit. Use cluster-admin only when you truly need it
Track your own credentialsCredentials you create yourself do not appear in the Portal — tracking them is on you
List what you createdkubectl get sa -l managed-by=customer --all-namespaces
StepAction
1Create the ServiceAccount and label it
2Grant the least permission that works
3Get a token, preferably one with a lifetime
4Build the kubeconfig and check it with kubectl auth can-i
5Import it into the target system
6Set 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.