From 93a1a2346c011ec2ae430e5706687d58dd93ac90 Mon Sep 17 00:00:00 2001 From: Maxime Le Nair Date: Sat, 24 Aug 2019 23:53:03 +0100 Subject: [PATCH 1/3] 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 | 78 +++++++++++++++++++ elasticsearch/values.yaml | 8 ++ 8 files changed, 186 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 7ac117df6..92a222611 100644 --- a/elasticsearch/README.md +++ b/elasticsearch/README.md @@ -112,6 +112,8 @@ helm install --name elasticsearch elastic/elasticsearch --set imageTag=7.3.0 | `masterTerminationFix` | A workaround needed for Elasticsearch < 7.2 to prevent master status being lost during restarts [#63](https://github.com/elastic/helm-charts/issues/63) | `false` | | `lifecycle` | Allows you to add lifecycle configuration. See [values.yaml](./values.yaml) for an example of the formatting. | `{}` | | `keystore` | Allows you map Kubernetes secrets into the keystore. See the [config example](/elasticsearch/examples/config/values.yaml) and [how to use the keystore](#how-to-use-the-keystore) | `[]` | +| `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 fbf8e6577..09637b2f5 100644 --- a/elasticsearch/templates/statefulset.yaml +++ b/elasticsearch/templates/statefulset.yaml @@ -58,6 +58,11 @@ spec: {{- if .Values.fsGroup }} fsGroup: {{ .Values.fsGroup }} # Deprecated value, please use .Values.podSecurityContext.fsGroup {{- end }} + {{- 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 a4949eef9..264700353 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' @@ -899,3 +900,80 @@ def test_keystore_volumes(): }] } } in s['volumes'] +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 7ce13c314..f8f454d95 100755 --- a/elasticsearch/values.yaml +++ b/elasticsearch/values.yaml @@ -86,6 +86,14 @@ volumeClaimTemplate: requests: storage: 30Gi +rbac: + create: false + serviceAccountName: "" + +podSecurityPolicy: + create: false + name: "" + persistence: enabled: true annotations: {} From e7f76dce3a69c451ff72830cbef57e62631663e1 Mon Sep 17 00:00:00 2001 From: Maxime Le Nair Date: Thu, 19 Sep 2019 11:49:10 +0100 Subject: [PATCH 2/3] Changes based on feedback. --- elasticsearch/README.md | 4 ++-- elasticsearch/templates/podsecuritypolicy.yaml | 16 +--------------- elasticsearch/tests/elasticsearch_test.py | 11 +---------- elasticsearch/values.yaml | 14 ++++++++++++++ 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/elasticsearch/README.md b/elasticsearch/README.md index 92a222611..fe23302af 100644 --- a/elasticsearch/README.md +++ b/elasticsearch/README.md @@ -112,8 +112,8 @@ helm install --name elasticsearch elastic/elasticsearch --set imageTag=7.3.0 | `masterTerminationFix` | A workaround needed for Elasticsearch < 7.2 to prevent master status being lost during restarts [#63](https://github.com/elastic/helm-charts/issues/63) | `false` | | `lifecycle` | Allows you to add lifecycle configuration. See [values.yaml](./values.yaml) for an example of the formatting. | `{}` | | `keystore` | Allows you map Kubernetes secrets into the keystore. See the [config example](/elasticsearch/examples/config/values.yaml) and [how to use the keystore](#how-to-use-the-keystore) | `[]` | -| `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: ""` +| `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 index 2f7e371dc..1e65b2864 100644 --- a/elasticsearch/templates/podsecuritypolicy.yaml +++ b/elasticsearch/templates/podsecuritypolicy.yaml @@ -10,19 +10,5 @@ metadata: 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 +{{ toYaml .Values.podSecurityPolicy.spec | indent 2 }} {{- end -}} diff --git a/elasticsearch/tests/elasticsearch_test.py b/elasticsearch/tests/elasticsearch_test.py index 264700353..88aabd1f4 100755 --- a/elasticsearch/tests/elasticsearch_test.py +++ b/elasticsearch/tests/elasticsearch_test.py @@ -3,7 +3,6 @@ sys.path.insert(1, os.path.join(sys.path[0], '../../helpers')) from helpers import helm_template import yaml -import json clusterName = 'elasticsearch' nodeGroup = 'master' @@ -902,15 +901,7 @@ def test_keystore_volumes(): } in s['volumes'] def test_pod_security_policy(): ## Make sure the default config is not creating any resources - config = ''' -rbac: - create: false - serviceAccountName: "" - -podSecurityPolicy: - create: false - name: "" -''' + config = '' resources = ('role', 'rolebinding', 'serviceaccount', 'podsecuritypolicy') r = helm_template(config) for resource in resources: diff --git a/elasticsearch/values.yaml b/elasticsearch/values.yaml index f8f454d95..306f76539 100755 --- a/elasticsearch/values.yaml +++ b/elasticsearch/values.yaml @@ -93,6 +93,20 @@ rbac: podSecurityPolicy: create: false name: "" + spec: + privileged: true + fsGroup: + rule: RunAsAny + runAsUser: + rule: RunAsAny + seLinux: + rule: RunAsAny + supplementalGroups: + rule: RunAsAny + volumes: + - secret + - configMap + - persistentVolumeClaim persistence: enabled: true From 02d215e3853f950399d99f99c73bc66b5e16aead Mon Sep 17 00:00:00 2001 From: Maxime Le Nair Date: Thu, 19 Sep 2019 12:27:37 +0100 Subject: [PATCH 3/3] Fix test following removal of some permissions in the PSP spec. --- elasticsearch/tests/elasticsearch_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/elasticsearch/tests/elasticsearch_test.py b/elasticsearch/tests/elasticsearch_test.py index 88aabd1f4..c194c6a6b 100755 --- a/elasticsearch/tests/elasticsearch_test.py +++ b/elasticsearch/tests/elasticsearch_test.py @@ -926,9 +926,7 @@ def test_pod_security_policy(): 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():