RBAC permissions for Helm

Page content

I this post I will demonstrate the basic mechanism of helm and Role-based access control (RBAC).

Parts of the Kubernetes series

I whant to use helm on Openshift but firt I startid with the basics of helm and Role-based access control (RBAC) on a simple Kubernestes cluster. Most people seem to be running Helm with their own credentials or a dedicated service account with cluster-admin permissions. This isn’t very good from a security perspective, especially so if it’s being run within CI/CD.

Helm

Helm is a package manager and teplating engine for Kubernetes. It based on tree main components:

  • the helm cli client
  • the helm server called tiller
  • the template pcakage called halm chart

Helm with cluster-admin permissions

nano helm-cluster-admin.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller-admin
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tiller-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: tiller-admin
  namespace: kube-system

Helm with namespace permissions

We are granting permissions on only the API groups and resources that Tiller needs to deploy and manage releases in its namespace.

nano helm-dev-namespace.yaml
kind: Namespace
apiVersion: v1
metadata:
  name: dev
---
kind: ServiceAccount
apiVersion: v1
metadata:
  name: tiller
  namespace: dev
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: tiller-manager
  namespace: dev
rules:
- apiGroups: ["", "batch", "extensions", "apps"]
  resources: ["*"]
  verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: tiller-binding
  namespace: dev
subjects:
- kind: ServiceAccount
  name: tiller
  namespace: dev
roleRef:
  kind: Role
  name: tiller-manager
  apiGroup: rbac.authorization.k8s.io
nano helm-prod-namespace.yaml
kind: Namespace
apiVersion: v1
metadata:
  name: prod
---
kind: ServiceAccount
apiVersion: v1
metadata:
  name: tiller
  namespace: prod
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: tiller-manager
  namespace: prod
rules:
- apiGroups: ["", "batch", "extensions", "apps"]
  resources: ["*"]
  verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: tiller-binding
  namespace: prod
subjects:
- kind: ServiceAccount
  name: tiller
  namespace: prod
roleRef:
  kind: Role
  name: tiller-manager
  apiGroup: rbac.authorization.k8s.io
kubectl create -f helm-dev-namespace.yaml
kubectl create -f helm-prod-namespace.yaml

kubectl -n dev get sa
kubectl -n prod get sa

helm init --service-account tiller --tiller-namespace dev
helm init --service-account tiller --tiller-namespace prod

Helm with minimal cluster permissions

nano helm-cluster-role.yml
kind: Namespace
apiVersion: v1
metadata:
  name: helm
---
kind: ServiceAccount
apiVersion: v1
metadata:
  name: helm
  namespace: helm
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: helm-clusterrole
rules:
  - apiGroups: [""]
    resources: ["pods/portforward"]
    verbs: ["create"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["list", "get"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: helm-clusterrolebinding
roleRef:
  kind: ClusterRole
  apiGroup: rbac.authorization.k8s.io
  name: helm-clusterrole
subjects:
  - kind: ServiceAccount
    name: helm
    namespace: helm

Generate a Kubeconfig file from the Helm Service Account

Credit to Ami Mahloof for this script.

NAMESPACE=helm
# Find the secret associated with the Service Account
SECRET=$(kubectl -n $NAMESPACE get sa helm -o jsonpath='{.secrets[].name}')
# Get the token from the secret
TOKEN=$(kubectl get secrets -n $NAMESPACE $SECRET -o jsonpath='{.data.token}' | base64 -D)
# Get the CA from the secret
kubectl get secrets -n $NAMESPACE $SECRET -o jsonpath='{.data.ca\.crt}' | base64 -D > ca.crt

CONTEXT=$(kubectl config current-context)
CLUSTER_NAME=$(kubectl config get-contexts $CONTEXT --no-headers=true | awk '{print $3}')
SERVER=$(kubectl config view -o jsonpath="{.clusters[?(@.name == \"${CLUSTER_NAME}\")].cluster.server}")
KUBECONFIG_FILE=config
USER=helm
CA=ca.crt

# Set up config
kubectl config set-cluster $CLUSTER_NAME \
--kubeconfig=$KUBECONFIG_FILE \
--server=$SERVER \
--certificate-authority=$CA \
--embed-certs=true

kubectl config set-credentials $USER \
--kubeconfig=$KUBECONFIG_FILE \
--token=$TOKEN

kubectl config set-context $USER \
--kubeconfig=$KUBECONFIG_FILE \
--cluster=$CLUSTER_NAME \
--user=$USER

kubectl config use-context $USER \
--kubeconfig=$KUBECONFIG_FILE