From 61a6aedaa5e3d29fb2524301d594fc616f4c09bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Neto=C4=8Dn=C3=BD?= Date: Fri, 2 Mar 2018 20:25:52 +0100 Subject: [PATCH] [stable/kubernetes-dashboard] Harden Kubernetes Dashboard security setup to protect whole cluster (#3935) * Updated helm chart for Kubernetes Dashboard to harden cluster security * Fixed typo * Modified to be in line with RBAC guidlines * Add link to guide * Fixed typo --- stable/kubernetes-dashboard/Chart.yaml | 4 +- stable/kubernetes-dashboard/README.md | 42 ++++++----- .../kubernetes-dashboard/templates/NOTES.txt | 8 +-- .../templates/_helpers.tpl | 18 +++++ .../templates/deployment.yaml | 34 ++++++--- .../templates/ingress.yaml | 2 +- .../kubernetes-dashboard/templates/role.yaml | 72 +++++++++++++++++++ .../templates/rolebinding.yaml | 28 +++++++- .../templates/secret.yaml | 11 +++ .../templates/serviceaccount.yaml | 7 +- .../kubernetes-dashboard/templates/svc.yaml | 4 +- stable/kubernetes-dashboard/values.yaml | 29 +++++--- 12 files changed, 210 insertions(+), 49 deletions(-) create mode 100644 stable/kubernetes-dashboard/templates/role.yaml create mode 100644 stable/kubernetes-dashboard/templates/secret.yaml diff --git a/stable/kubernetes-dashboard/Chart.yaml b/stable/kubernetes-dashboard/Chart.yaml index 3ad4f4bddf..bec2d8c95f 100644 --- a/stable/kubernetes-dashboard/Chart.yaml +++ b/stable/kubernetes-dashboard/Chart.yaml @@ -1,6 +1,6 @@ name: kubernetes-dashboard -version: 0.5.3 -appVersion: 1.8.1 +version: 0.6.0 +appVersion: 1.8.3 description: General-purpose web UI for Kubernetes clusters keywords: - kubernetes diff --git a/stable/kubernetes-dashboard/README.md b/stable/kubernetes-dashboard/README.md index 332dc918ec..22c11d0432 100644 --- a/stable/kubernetes-dashboard/README.md +++ b/stable/kubernetes-dashboard/README.md @@ -33,26 +33,34 @@ $ helm delete my-release The command removes all the Kubernetes components associated with the chart and deletes the release. +## Access control +It is critical for the Kubernetes custer to correctly setup access control of Kubernetes Dashboard. See this [guide](https://github.com/kubernetes/dashboard/wiki/Access-control) for best practises. + +It is highly recommended to use RBAC with minimal privileges needed for Dashboard to run. + ## Configuration -The following tables lists the configurable parameters of the kubernetes-dashboard chart and their default values. +The following table lists the configurable parameters of the kubernetes-dashboard chart and their default values. -| Parameter | Description | Default | -|------------------------|------------------------------------|--------------------------------------------------------------------------| -| `image.repository` | Repository for container image | `gcr.io/google_containers/kubernetes-dashboard-amd64` | -| `image.tag` | Image tag | `v1.8.1` | -| `image.pullPolicy` | Image pull policy | `IfNotPresent` | -| `extraArgs` | Additional container arguments | `[]` | -| `nodeSelector` | node labels for pod assignment | `{}` | -| `service.externalPort` | Dashboard internal port | 80 | -| `service.internalPort` | Dashboard external port | 80 | -| `ingress.annotations` | Specify ingress class | `kubernetes.io/ingress.class: nginx` | -| `ingress.enabled` | Enable ingress controller resource | `false` | -| `ingress.hosts` | Dashboard Hostnames | `nil` | -| `ingress.tls` | Ingress TLS configuration | `[]` | -| `resources` | Pod resource requests & limits | `limits: {cpu: 100m, memory: 50Mi}, requests: {cpu: 100m, memory: 50Mi}` | -| `rbac.create` | Create & use RBAC resources | `false` | -| `rbac.serviceAccountName` | ServiceAccount kubernetes-dashboard will use (ignored if rbac.create=true) | `default` | +| Parameter | Description | Default | +|---------------------------|-----------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| `image.repository` | Repository for container image | `k8s.gcr.io/kubernetes-dashboard-amd64` | +| `image.tag` | Image tag | `v1.8.3` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `extraArgs` | Additional container arguments | `[]` | +| `nodeSelector` | node labels for pod assignment | `{}` | +| `tolerations` | List of node taints to tolerate (requires Kubernetes >= 1.6) | `[]` | +| `service.externalPort` | Dashboard internal port | 443 | +| `service.internalPort` | Dashboard external port | 443 | +| `ingress.annotations` | Specify ingress class | `kubernetes.io/ingress.class: nginx` | +| `ingress.enabled` | Enable ingress controller resource | `false` | +| `ingress.hosts` | Dashboard Hostnames | `nil` | +| `ingress.tls` | Ingress TLS configuration | `[]` | +| `resources` | Pod resource requests & limits | `limits: {cpu: 100m, memory: 50Mi}, requests: {cpu: 100m, memory: 50Mi}` | +| `rbac.create` | Create & use RBAC resources | `true` | +| `rbac.clusterAdminRole` | "cluster-admin" ClusterRole will be used for dashboard ServiceAccount ([NOT RECOMMENDED](#access-control)) | `false` | +| `serviceAccount.create` | Whether a new service account name that the agent will use should be created. | `true` | +| `serviceAccount.name` | Service account to be used. If not set and serviceAccount.create is `true` a name is generated using the fullname template. | | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, diff --git a/stable/kubernetes-dashboard/templates/NOTES.txt b/stable/kubernetes-dashboard/templates/NOTES.txt index d1637c4975..2bd12ab080 100644 --- a/stable/kubernetes-dashboard/templates/NOTES.txt +++ b/stable/kubernetes-dashboard/templates/NOTES.txt @@ -5,7 +5,7 @@ {{- if .Values.ingress.enabled }} From outside the cluster, the server URL(s) are: {{- range .Values.ingress.hosts }} - http://{{ . }} + https://{{ . }} {{- end }} {{- else if contains "NodePort" .Values.service.type }} @@ -13,7 +13,7 @@ From outside the cluster, the server URL(s) are: Get the Kubernetes Dashboard URL by running: export NODE_PORT=$(kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "kubernetes-dashboard.fullname" . }}) export NODE_IP=$(kubectl get nodes -o jsonpath="{.items[0].status.addresses[0].address}") - echo http://$NODE_IP:$NODE_PORT/ + echo https://$NODE_IP:$NODE_PORT/ {{- else if contains "LoadBalancer" .Values.service.type }} @@ -22,11 +22,11 @@ Get the Kubernetes Dashboard URL by running: Get the Kubernetes Dashboard URL by running: export SERVICE_IP=$(kubectl get svc {{ template "kubernetes-dashboard.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - echo http://$SERVICE_IP/ + echo https://$SERVICE_IP/ {{- else if contains "ClusterIP" .Values.service.type }} Get the Kubernetes Dashboard URL by running: export POD_NAME=$(kubectl get pods -n {{ .Release.Namespace }} -l "app={{ template "kubernetes-dashboard.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") - echo http://127.0.0.1:9090/ + echo https://127.0.0.1:9090/ kubectl -n {{ .Release.Namespace }} port-forward $POD_NAME 9090:9090 {{- end }} diff --git a/stable/kubernetes-dashboard/templates/_helpers.tpl b/stable/kubernetes-dashboard/templates/_helpers.tpl index 1848820b9d..0326494d4f 100644 --- a/stable/kubernetes-dashboard/templates/_helpers.tpl +++ b/stable/kubernetes-dashboard/templates/_helpers.tpl @@ -23,3 +23,21 @@ If release name contains chart name it will be used as a full name. {{- end -}} {{- end -}} {{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "kubernetes-dashboard.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "kubernetes-dashboard.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "kubernetes-dashboard.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/stable/kubernetes-dashboard/templates/deployment.yaml b/stable/kubernetes-dashboard/templates/deployment.yaml index 566c2dc021..862ac7422c 100644 --- a/stable/kubernetes-dashboard/templates/deployment.yaml +++ b/stable/kubernetes-dashboard/templates/deployment.yaml @@ -4,7 +4,7 @@ metadata: name: {{ template "kubernetes-dashboard.fullname" . }} labels: app: {{ template "kubernetes-dashboard.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + chart: {{ template "kubernetes-dashboard.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} kubernetes.io/cluster-service: "true" @@ -25,28 +25,32 @@ spec: release: {{ .Release.Name }} kubernetes.io/cluster-service: "true" spec: - serviceAccountName: {{ if .Values.rbac.create }}{{ template "kubernetes-dashboard.fullname" . }}{{ else }}"{{ .Values.rbac.serviceAccountName }}"{{ end }} + serviceAccountName: {{ template "kubernetes-dashboard.serviceAccountName" . }} containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} -{{- if .Values.extraArgs }} args: + - --auto-generate-certificates +{{- if .Values.extraArgs }} {{ toYaml .Values.extraArgs | indent 10 }} {{- end }} ports: - - name: http - containerPort: 9090 + - name: https + containerPort: 8443 protocol: TCP + volumeMounts: + - name: kubernetes-dashboard-certs + mountPath: /certs + # Create on-disk volume to store exec logs + - mountPath: /tmp + name: tmp-volume livenessProbe: - failureThreshold: 3 httpGet: + scheme: HTTPS path: / - port: 9090 - scheme: HTTP + port: 8443 initialDelaySeconds: 30 - periodSeconds: 10 - successThreshold: 1 timeoutSeconds: 30 resources: {{ toYaml .Values.resources | indent 10 }} @@ -54,3 +58,13 @@ spec: nodeSelector: {{ toYaml .Values.nodeSelector | indent 8 }} {{- end }} + volumes: + - name: kubernetes-dashboard-certs + secret: + secretName: {{ template "kubernetes-dashboard.fullname" . }} + - name: tmp-volume + emptyDir: {} + {{- if .Values.tolerations }} + tolerations: +{{ toYaml .Values.tolerations | indent 8 }} + {{- end }} diff --git a/stable/kubernetes-dashboard/templates/ingress.yaml b/stable/kubernetes-dashboard/templates/ingress.yaml index 94887e4ec0..3880079bcc 100644 --- a/stable/kubernetes-dashboard/templates/ingress.yaml +++ b/stable/kubernetes-dashboard/templates/ingress.yaml @@ -7,7 +7,7 @@ metadata: name: {{ template "kubernetes-dashboard.fullname" . }} labels: app: {{ template "kubernetes-dashboard.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + chart: {{ template "kubernetes-dashboard.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} {{- if .Values.ingress.annotations }} diff --git a/stable/kubernetes-dashboard/templates/role.yaml b/stable/kubernetes-dashboard/templates/role.yaml new file mode 100644 index 0000000000..4f5fdc03e4 --- /dev/null +++ b/stable/kubernetes-dashboard/templates/role.yaml @@ -0,0 +1,72 @@ +{{- if and .Values.rbac.create (not .Values.rbac.clusterAdminRole) }} +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + labels: + app: {{ template "kubernetes-dashboard.name" . }} + chart: {{ template "kubernetes-dashboard.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + name: {{ template "kubernetes-dashboard.fullname" . }} + namespace: {{ .Release.Namespace }} +rules: + # Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret. +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + + # Allow Dashboard to create 'kubernetes-dashboard-settings' config map. +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + + # Allow Dashboard to get, update and delete Dashboard exclusive secrets. +- apiGroups: + - "" + resources: + - secrets + resourceNames: + - kubernetes-dashboard-key-holder + - {{ template "kubernetes-dashboard.fullname" . }} + verbs: + - get + - update + - delete + + # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map. +- apiGroups: + - "" + resources: + - configmaps + resourceNames: + - kubernetes-dashboard-settings + verbs: + - get + - update + + # Allow Dashboard to get metrics from heapster. +- apiGroups: + - "" + resources: + - services + resourceNames: + - heapster + verbs: + - proxy +- apiGroups: + - "" + resources: + - services/proxy + resourceNames: + - heapster + - "http:heapster:" + - "https:heapster:" + verbs: + - get +{{- end -}} diff --git a/stable/kubernetes-dashboard/templates/rolebinding.yaml b/stable/kubernetes-dashboard/templates/rolebinding.yaml index b5e5f9efd7..52deb8c41c 100644 --- a/stable/kubernetes-dashboard/templates/rolebinding.yaml +++ b/stable/kubernetes-dashboard/templates/rolebinding.yaml @@ -1,10 +1,13 @@ {{- if .Values.rbac.create }} + +{{- if .Values.rbac.clusterAdminRole }} +# Cluster role binding for clusterAdminRole == true apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: labels: app: {{ template "kubernetes-dashboard.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + chart: {{ template "kubernetes-dashboard.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} name: {{ template "kubernetes-dashboard.fullname" . }} @@ -14,6 +17,27 @@ roleRef: name: cluster-admin subjects: - kind: ServiceAccount - name: {{ template "kubernetes-dashboard.fullname" . }} + name: {{ template "kubernetes-dashboard.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- else -}} +# Role binding for clusterAdminRole == false +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + labels: + app: {{ template "kubernetes-dashboard.name" . }} + chart: {{ template "kubernetes-dashboard.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + name: {{ template "kubernetes-dashboard.fullname" . }} + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "kubernetes-dashboard.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "kubernetes-dashboard.serviceAccountName" . }} namespace: {{ .Release.Namespace }} {{- end -}} +{{- end -}} diff --git a/stable/kubernetes-dashboard/templates/secret.yaml b/stable/kubernetes-dashboard/templates/secret.yaml new file mode 100644 index 0000000000..fc8f5de534 --- /dev/null +++ b/stable/kubernetes-dashboard/templates/secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + labels: + app: {{ template "kubernetes-dashboard.name" . }} + chart: {{ template "kubernetes-dashboard.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + name: {{ template "kubernetes-dashboard.fullname" . }} + namespace: {{ .Release.Namespace }} +type: Opaque diff --git a/stable/kubernetes-dashboard/templates/serviceaccount.yaml b/stable/kubernetes-dashboard/templates/serviceaccount.yaml index 59bd54b901..800f6e28d0 100644 --- a/stable/kubernetes-dashboard/templates/serviceaccount.yaml +++ b/stable/kubernetes-dashboard/templates/serviceaccount.yaml @@ -1,11 +1,12 @@ -{{- if .Values.rbac.create }} +{{- if .Values.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount metadata: labels: app: {{ template "kubernetes-dashboard.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + chart: {{ template "kubernetes-dashboard.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} - name: {{ template "kubernetes-dashboard.fullname" . }} + name: {{ template "kubernetes-dashboard.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} {{- end -}} diff --git a/stable/kubernetes-dashboard/templates/svc.yaml b/stable/kubernetes-dashboard/templates/svc.yaml index bc99ab385f..876a617e08 100644 --- a/stable/kubernetes-dashboard/templates/svc.yaml +++ b/stable/kubernetes-dashboard/templates/svc.yaml @@ -4,7 +4,7 @@ metadata: name: {{ template "kubernetes-dashboard.fullname" . }} labels: app: {{ template "kubernetes-dashboard.name" . }} - chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} + chart: {{ template "kubernetes-dashboard.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} kubernetes.io/cluster-service: "true" @@ -19,7 +19,7 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.externalPort }} - targetPort: http + targetPort: https {{- if hasKey .Values.service "nodePort" }} nodePort: {{ .Values.service.nodePort }} {{- end }} diff --git a/stable/kubernetes-dashboard/values.yaml b/stable/kubernetes-dashboard/values.yaml index 3dc3ed0a11..d80a31eec3 100644 --- a/stable/kubernetes-dashboard/values.yaml +++ b/stable/kubernetes-dashboard/values.yaml @@ -5,7 +5,7 @@ image: repository: k8s.gcr.io/kubernetes-dashboard-amd64 - tag: v1.8.1 + tag: v1.8.3 pullPolicy: IfNotPresent ## Here labels can be added to the kubernets dashboard deployment @@ -25,9 +25,16 @@ labels: {} ## nodeSelector: {} +## List of node taints to tolerate (requires Kubernetes >= 1.6) +tolerations: [] +# - key: "key" +# operator: "Equal|Exists" +# value: "value" +# effect: "NoSchedule|PreferNoSchedule|NoExecute" + service: type: ClusterIP - externalPort: 80 + externalPort: 443 ## This allows an overide of the heapster service name ## Default: {{ .Chart.Name }} @@ -78,10 +85,16 @@ ingress: # - kubernetes-dashboard.domain.com rbac: - ## If true, create & use RBAC resources - # - create: false + # Specifies whether RBAC resources should be created + create: true - ## Ignored if rbac.create is true - # - serviceAccountName: default + # Specifies whether cluster-admin ClusterRole will be used for dashboard + # ServiceAccount (NOT RECOMMENDED). + clusterAdminRole: false + +serviceAccount: + # Specifies whether a service account should be created + create: true + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: