Post

A Shared Kubeconfig for Pinniped Users

A Shared Kubeconfig for Pinniped Users

pinniped

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:

  1. User needs a kubeconfig to connect to the cluster
  2. User runs pinniped get kubeconfig with their credentials
  3. This generates a new kubeconfig with their identity
  4. 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:

  1. Create a ServiceAccount with minimal RBAC (only Pinniped permissions)
  2. Generate a kubeconfig for this ServiceAccount
  3. Share this kubeconfig with your team
  4. Users use it to run pinniped get kubeconfig and get the Pinniped kubeconfig
  5. 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 kubeconfig command requires CA certificate in the kubeconfig. Make sure certificate-authority-data is 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-data is present (required for Pinniped)
  • token is present in the user section
  • insecure-skip-tls-verify: false (use true only if your cluster uses self-signed certs without proper CA)

Make sure both certificate-authority-data and token are 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:

  1. Minimal permissions - The shared ServiceAccount can only run Pinniped commands
  2. No cluster access - It cannot read secrets, create pods, or access any cluster resources
  3. One-time use - Users use it once to bootstrap, then use the Pinniped kubeconfig
  4. 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.

Resources