From fc06f2068a8c4ca7a2c65c6d5e3092f7dc2be1cf Mon Sep 17 00:00:00 2001 From: Maxime Le Nair Date: Sat, 24 Aug 2019 22:38:28 +0100 Subject: [PATCH] Hardening of the pod permissions. Role, role binding and service account can now be created by the Helm chart. An external service account can also be referenced. Adding support for pod security policy to either be referenced from an existing one or created by the helm chart. --- elasticsearch/README.md | 2 + .../templates/podsecuritypolicy.yaml | 28 +++++++ elasticsearch/templates/role.yaml | 25 ++++++ elasticsearch/templates/rolebinding.yaml | 24 ++++++ elasticsearch/templates/serviceaccount.yaml | 16 ++++ elasticsearch/templates/statefulset.yaml | 5 ++ elasticsearch/tests/elasticsearch_test.py | 80 +++++++++++++++++++ elasticsearch/values.yaml | 8 ++ 8 files changed, 188 insertions(+) create mode 100644 elasticsearch/templates/podsecuritypolicy.yaml create mode 100644 elasticsearch/templates/role.yaml create mode 100644 elasticsearch/templates/rolebinding.yaml create mode 100644 elasticsearch/templates/serviceaccount.yaml diff --git a/elasticsearch/README.md b/elasticsearch/README.md index f8024616e..c429d2bb5 100644 --- a/elasticsearch/README.md +++ b/elasticsearch/README.md @@ -100,6 +100,8 @@ helm install --name elasticsearch elastic/elasticsearch --version 7.1.0 --set im | `nodeSelector` | Configurable [nodeSelector](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) so that you can target specific nodes for your Elasticsearch cluster | `{}` | | `tolerations` | Configurable [tolerations](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) | `[]` | | `ingress` | Configurable [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) to expose the Elasticsearch service. See [`values.yaml`](./values.yaml) for an example | `enabled: false` | +| `rbac` | Configuration for creating a role, role binding and service account as part of this helm chart with `create: true`. Also can be used to reference an external service account with `serviceAccountName: "externalServiceAccountName"`. | `create: false`
`serviceAccountName: ""` +| `podSecurityPolicy` | Configuration for create a pod security policy with minimal permissions to run this Helm chart with `create: true`. Also can be used to reference an external pod security policy with `name: "externalPodSecurityPolicy"` | `create: false`
`name: ""` ## Try it out diff --git a/elasticsearch/templates/podsecuritypolicy.yaml b/elasticsearch/templates/podsecuritypolicy.yaml new file mode 100644 index 000000000..2f7e371dc --- /dev/null +++ b/elasticsearch/templates/podsecuritypolicy.yaml @@ -0,0 +1,28 @@ +{{- if .Values.podSecurityPolicy.create -}} +{{- $fullName := include "uname" . -}} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ default $fullName .Values.podSecurityPolicy.name | quote }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app: {{ $fullName | quote }} +spec: + allowPrivilegeEscalation: true + privileged: true + allowedCapabilities: ['*'] + fsGroup: + rule: RunAsAny + runAsUser: + rule: RunAsAny + seLinux: + rule: RunAsAny + supplementalGroups: + rule: RunAsAny + volumes: + - secret + - configMap + - persistentVolumeClaim +{{- end -}} diff --git a/elasticsearch/templates/role.yaml b/elasticsearch/templates/role.yaml new file mode 100644 index 000000000..d616e80d3 --- /dev/null +++ b/elasticsearch/templates/role.yaml @@ -0,0 +1,25 @@ +{{- if .Values.rbac.create -}} +{{- $fullName := include "uname" . -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ $fullName | quote }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app: {{ $fullName | quote }} +rules: + - apiGroups: + - extensions + resources: + - podsecuritypolicies + resourceNames: + {{- if eq .Values.podSecurityPolicy.name "" }} + - {{ $fullName | quote }} + {{- else }} + - {{ .Values.podSecurityPolicy.name | quote }} + {{- end }} + verbs: + - use +{{- end -}} diff --git a/elasticsearch/templates/rolebinding.yaml b/elasticsearch/templates/rolebinding.yaml new file mode 100644 index 000000000..c2b6070a4 --- /dev/null +++ b/elasticsearch/templates/rolebinding.yaml @@ -0,0 +1,24 @@ +{{- if .Values.rbac.create -}} +{{- $fullName := include "uname" . -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $fullName | quote }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app: {{ $fullName | quote }} +subjects: + - kind: ServiceAccount + {{- if eq .Values.rbac.serviceAccountName "" }} + name: {{ $fullName | quote }} + {{- else }} + name: {{ .Values.rbac.serviceAccountName | quote }} + {{- end }} + namespace: {{ .Release.Namespace | quote }} +roleRef: + kind: Role + name: {{ $fullName | quote }} + apiGroup: rbac.authorization.k8s.io +{{- end -}} diff --git a/elasticsearch/templates/serviceaccount.yaml b/elasticsearch/templates/serviceaccount.yaml new file mode 100644 index 000000000..59bbd5326 --- /dev/null +++ b/elasticsearch/templates/serviceaccount.yaml @@ -0,0 +1,16 @@ +{{- if .Values.rbac.create -}} +{{- $fullName := include "uname" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- if eq .Values.rbac.serviceAccountName "" }} + name: {{ $fullName | quote }} + {{- else }} + name: {{ .Values.rbac.serviceAccountName | quote }} + {{- end }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app: {{ $fullName | quote }} +{{- end -}} diff --git a/elasticsearch/templates/statefulset.yaml b/elasticsearch/templates/statefulset.yaml index 00f552f28..cd8bb4341 100644 --- a/elasticsearch/templates/statefulset.yaml +++ b/elasticsearch/templates/statefulset.yaml @@ -47,6 +47,11 @@ spec: spec: securityContext: fsGroup: {{ .Values.fsGroup }} + {{- if .Values.rbac.create }} + serviceAccountName: "{{ template "uname" . }}" + {{- else if not (eq .Values.rbac.serviceAccountName "") }} + serviceAccountName: {{ .Values.rbac.serviceAccountName | quote }} + {{- end }} {{- with .Values.tolerations }} tolerations: {{ toYaml . | indent 6 }} diff --git a/elasticsearch/tests/elasticsearch_test.py b/elasticsearch/tests/elasticsearch_test.py index 2232a89e2..8bbe4f362 100755 --- a/elasticsearch/tests/elasticsearch_test.py +++ b/elasticsearch/tests/elasticsearch_test.py @@ -3,6 +3,7 @@ sys.path.insert(1, os.path.join(sys.path[0], '../../helpers')) from helpers import helm_template import yaml +import json clusterName = 'elasticsearch' nodeGroup = 'master' @@ -569,3 +570,82 @@ def test_priority_class_name(): r = helm_template(config) priority_class_name = r['statefulset'][uname]['spec']['template']['spec']['priorityClassName'] assert priority_class_name == "highest" + + +def test_pod_security_policy(): + ## Make sure the default config is not creating any resources + config = ''' +rbac: + create: false + serviceAccountName: "" + +podSecurityPolicy: + create: false + name: "" +''' + resources = ('role', 'rolebinding', 'serviceaccount', 'podsecuritypolicy') + r = helm_template(config) + for resource in resources: + assert resource not in r + assert 'serviceAccountName' not in r['statefulset'][uname]['spec']['template']['spec'] + + ## Make sure all the resources are created with default values + config = ''' +rbac: + create: true + serviceAccountName: "" + +podSecurityPolicy: + create: true + name: "" +''' + r = helm_template(config) + for resource in resources: + assert resource in r + assert r['role'][uname]['rules'][0] == {"apiGroups": ["extensions"], "verbs": ["use"], "resources": ["podsecuritypolicies"], "resourceNames": [uname]} + assert r['rolebinding'][uname]['subjects'] == [{"kind": "ServiceAccount", "namespace": "default", "name": uname}] + assert r['rolebinding'][uname]['roleRef'] == {"apiGroup": "rbac.authorization.k8s.io", "kind": "Role", "name": uname} + assert r['statefulset'][uname]['spec']['template']['spec']['serviceAccountName'] == uname + psp_spec = r['podsecuritypolicy'][uname]['spec'] + assert psp_spec['allowPrivilegeEscalation'] is True + assert psp_spec['privileged'] is True + assert psp_spec['allowedCapabilities'] == ['*'] + + +def test_external_pod_security_policy(): + ## Make sure we can use an externally defined pod security policy + config = ''' +rbac: + create: true + serviceAccountName: "" + +podSecurityPolicy: + create: false + name: "customPodSecurityPolicy" +''' + resources = ('role', 'rolebinding') + r = helm_template(config) + for resource in resources: + assert resource in r + + assert r['role'][uname]['rules'][0] == {"apiGroups": ["extensions"], "verbs": ["use"], "resources": ["podsecuritypolicies"], "resourceNames": ["customPodSecurityPolicy"]} + + +def test_external_service_account(): + ## Make sure we can use an externally defined service account + config = ''' +rbac: + create: false + serviceAccountName: "customServiceAccountName" + +podSecurityPolicy: + create: false + name: "" +''' + resources = ('role', 'rolebinding', 'serviceaccount') + r = helm_template(config) + + assert r['statefulset'][uname]['spec']['template']['spec']['serviceAccountName'] == "customServiceAccountName" + # When referencing an external service account we do not want any resources to be created. + for resource in resources: + assert resource not in r diff --git a/elasticsearch/values.yaml b/elasticsearch/values.yaml index af8c6e1ff..f80135f70 100755 --- a/elasticsearch/values.yaml +++ b/elasticsearch/values.yaml @@ -75,6 +75,14 @@ volumeClaimTemplate: requests: storage: 30Gi +rbac: + create: false + serviceAccountName: "" + +podSecurityPolicy: + create: false + name: "" + persistence: enabled: true annotations: {}