A Shared Kubeconfig for Pinniped Users
Pinniped is a great tool for Kubernetes authentication. It lets users authenticate with external identity providers like LDAP, OIDC, or Active Directory.
But there’s a bootstrap problem: to run pinniped get kubeconfig, you need a kubeconfig file first. This creates a chicken-and-egg situation. How do you get the initial kubeconfig to run Pinniped commands?
This post shows a solution: create a shared kubeconfig with minimal permissions that only allows running pinniped get kubeconfig. Users can use this bootstrap kubeconfig to get the Pinniped kubeconfig, then use that for cluster access.
The Bootstrap Problem
When you set up Pinniped, the flow looks like this:
- User needs a kubeconfig to connect to the cluster
- User runs
pinniped get kubeconfigwith their credentials - This generates a new kubeconfig with their identity
- User switches to the new kubeconfig
But step 1 is the problem. Where does the initial kubeconfig come from?
This post shows how to create a minimal bootstrap kubeconfig that only allows running pinniped get kubeconfig. This solves the bootstrap problem while keeping permissions minimal.
The Solution
After your cluster is created, you can create a shared bootstrap kubeconfig with minimal permissions. This kubeconfig can only run pinniped get kubeconfig command - nothing else.
The flow is simple:
- Create a ServiceAccount with minimal RBAC (only Pinniped permissions)
- Generate a kubeconfig for this ServiceAccount
- Share this kubeconfig with your team
- Users use it to run
pinniped get kubeconfigand get the Pinniped kubeconfig - Users use the Pinniped kubeconfig for cluster access
Step 1: Create ServiceAccount and Secret
First, create a ServiceAccount that will be used for the shared kubeconfig. Also create a Secret to hold the token:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: ServiceAccount
automountServiceAccountToken: false
metadata:
name: user
namespace: kube-system
---
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: user-token
namespace: kube-system
annotations:
kubernetes.io/service-account.name: user
Step 2: Create RBAC Rules
Now give this ServiceAccount the right permissions. The ServiceAccount needs to:
- Read Pinniped kubeconfigs
- Read Pinniped configuration (credential issuers, identity providers)
Here are the RBAC rules to use:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: user
rules:
# Minimum required permissions for Pinniped
- apiGroups: ["pinniped.dev"]
resources: ["kubeconfigs"]
verbs: ["get"]
# Pinniped configuration read permissions
- apiGroups: ["config.pinniped.dev"]
resources: ["credentialissuerconfigs"]
verbs: ["get", "list"]
# Pinniped concierge permissions
- apiGroups: ["config.concierge.pinniped.dev"]
resources: ["credentialissuers"]
verbs: ["get", "list"]
# Pinniped authentication permissions
- apiGroups: ["authentication.concierge.pinniped.dev"]
resources: ["jwtauthenticators"]
verbs: ["get", "list"]
# Pinniped identity provider information read permissions
- apiGroups: ["config.pinniped.dev"]
resources: ["oidcidentityproviders", "ldapidentityproviders", "activedirectoryidentityproviders"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: user
roleRef:
kind: ClusterRole
name: user
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: user
namespace: kube-system
The key is to give only the minimum permissions needed. This ServiceAccount can only read Pinniped configuration and request kubeconfigs. It cannot read secrets, create pods, or access any other cluster resources.
Step 3: Generate Kubeconfig
Once you have the ServiceAccount, Secret, and RBAC, generate a kubeconfig file. Important: Pinniped requires both a token and CA certificate in the kubeconfig. Token-only kubeconfigs won’t work.
Since the Secret was created manually, wait a moment for Kubernetes to populate it with the token:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Get the ServiceAccount token from the Secret
TOKEN=$(kubectl get secret user-token -n kube-system -o jsonpath='{.data.token}' | base64 -d)
# Get cluster info including CA certificate
CLUSTER_NAME=$(kubectl config view --minify -o jsonpath='{.clusters[0].name}')
SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
CA_DATA=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
# Create kubeconfig with token AND CA certificate
kubectl config set-credentials user --token=$TOKEN
kubectl config set-cluster $CLUSTER_NAME --server=$SERVER --certificate-authority-data=$CA_DATA
kubectl config set-context user --cluster=$CLUSTER_NAME --user=user
kubectl config use-context user
# Save to file
kubectl config view --flatten > shared-pinniped-kubeconfig.yaml
Important: Pinniped
get kubeconfigcommand requires CA certificate in the kubeconfig. Make surecertificate-authority-datais set in the cluster configuration. Token-only kubeconfigs will not work.
This creates a kubeconfig file that uses the ServiceAccount token and includes the CA certificate.
Example Kubeconfig Structure
Here’s what the generated kubeconfig should look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
clusters:
- cluster:
insecure-skip-tls-verify: false
server: https://your-cluster-server:6443
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
name: your-cluster-name
contexts:
- context:
cluster: your-cluster-name
user: user
name: user@your-cluster-name
current-context: user@your-cluster-name
kind: Config
preferences: {}
users:
- name: user
user:
token: eyJhbGciOiJSUzI1NiIsImtpZCI6...
Key points:
certificate-authority-datais present (required for Pinniped)tokenis present in the user sectioninsecure-skip-tls-verify: false(usetrueonly if your cluster uses self-signed certs without proper CA)
Make sure both
certificate-authority-dataandtokenare present. Without the CA certificate, Pinniped will fail.
Step 4: Test the Kubeconfig
Before sharing, let’s test that it works:
1
2
3
4
5
6
7
# Use the shared kubeconfig
export KUBECONFIG=./shared-pinniped-kubeconfig.yaml
# Try to run pinniped get kubeconfig
pinniped get kubeconfig
# This should work and generate the Pinniped kubeconfig
If this works, the kubeconfig is ready to share.
Security Considerations
This approach is safe because:
- Minimal permissions - The shared ServiceAccount can only run Pinniped commands
- No cluster access - It cannot read secrets, create pods, or access any cluster resources
- One-time use - Users use it once to bootstrap, then use the Pinniped kubeconfig
- Audit trail - All actions are logged with the ServiceAccount identity
Even though the bootstrap kubeconfig is shared, it’s safe because it has very limited permissions. The Pinniped kubeconfig is also safe to share because users authenticate through the identity provider when accessing the cluster.
