Generate Kubeconfig For Tiller Service Account

In this post, I’ll discuss how to get a kubeconfig for helm tiller service account.

In Kubernetes, it’s a best practice granting a role to an application-specific service account. By creating a service account for designated operations ensures your application operating tasks in a particular scope. In my case, I deploy applications into AWS EKS via Helm. The essential steps for configuring RBAC for EKS is described in Helm: Role-Based Access Control.

I suddenly realize that my kubeconfig looks like below, which is nothing special as the one introduced in Getting Started with Amazon EKS:

apiVersion: v1
clusters:
- cluster:
    server: https://000000000000000000000000000.sk1.eu-west-1.eks.amazonaws.com
    certificate-authority-data: [REDACTED]
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: aws
  name: aws
current-context: aws
kind: Config
preferences: {}
users:
- name: aws
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1alpha1
      command: aws-iam-authenticator
      args:
        - "token"
        - "-i"
        - "some-kinda-k8s-cluster"
        - "-r"
        - "arn:aws:iam::000000000000:role/SomeKindaRole"

In our security model, any AWS Operation must go through an AWS federated login switching to a dedicated IAM Role. It also requires me running okta2aws login for obtaining a temporary credential for a few hours.

It’s unlikely I can complete authentication in Jenkins server because okta2aws login requires either Phone Approval or Ubikey Approval. So my way is to take a detour, use the tiller service account created above! In short, we save tiller service account secret in Jenkins for authentication.

The secret is Every k8s ServiceAccount has a secret.

$ k get serviceaccount tiller -o yaml
apiVersion: v1
kind: ServiceAccount
...(truncated)
secrets:
- name: tiller-token-lkeck

$ k get secret tiller-token-lkeck
apiVersion: v1
data:
  ca.crt: xxxx=
  namespace: Blah=
  token: BlahBlahBlah=
kind: Secret

The ca.crt, namespace, token are encoded in base64. You can get token by running base64 command:

$ TOKEN=`echo 'BlahBlahBlah=' | base64 --decode`
$ curl -k -XPOST  -H "Content-Type: application/json" -H "Accept: application/json, */*" -H "User-Agent: kubectl/v1.11.3 (darwin/amd64) kubernetes/a452946" 'https://000000000000000000000000000.sk1.eu-west-1.eks.amazonaws.com' -d '{"kind":"SelfSubjectAccessReview","apiVersion":"authorization.k8s.io/v1","metadata":{"creationTimestamp":null},"spec":{"resourceAttributes":{"namespace":"a-kinda-namespace","verb":"get","resource":"pods"}},"status":{"allowed":false}}' -H"Authorization: Bearer $TOKEN

Above curl command is somewhat equivalent to k auth can-i get pod. And the answer is yes, because the same token just let me pass the authentication.

apiVersion: v1
clusters:
- cluster:
    server: https://000000000000000000000000000.sk1.eu-west-1.eks.amazonaws.com
    certificate-authority-data: [REDACTED]
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    namespace: a-kinda-namespace
    user: tiller
  name: tiller
current-context: a-kinda-namespace
kind: Config
preferences: {}
users:
- name: tiller
  user:
    token: ${REPLACE ABOVE TOKEN HERE}

Note that the certificate-authority-data should be base64-encoded, as the original content of ca.crt being in k get secret. However, the token should be base64-decoded. Guess the reason is the token is a JWT encoded message which has filtered illegal characters.

Both kubectl and helm can use the second kubeconfig for operations which has less scope and, of course, more secure!