Skip to content
This repository has been archived by the owner on May 16, 2023. It is now read-only.

[elasticsearch] Keystore integration #154

Merged
merged 2 commits into from
Aug 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 50 additions & 12 deletions elasticsearch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ helm install --name elasticsearch elastic/elasticsearch --set imageTag=7.3.0
| `schedulerName` | Name of the [alternate scheduler](https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/#specify-schedulers-for-pods) | `nil` |
| `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) | `[]` |

## Try it out

Expand Down Expand Up @@ -171,18 +172,55 @@ There are a couple reasons we recommend this.

#### How to use the keystore?

1. Create a Kubernetes secret containing the [keystore](https://www.elastic.co/guide/en/elasticsearch/reference/current/secure-settings.html)
```
$ kubectl create secret generic elasticsearch-keystore --from-file=./elasticsearch.keystore
```
2. Mount it into the container via `secretMounts`
```
secretMounts:
- name: elasticsearch-keystore
secretName: elasticsearch-keystore
path: /usr/share/elasticsearch/config/elasticsearch.keystore
subPath: elasticsearch.keystore
```

##### Basic example

Create the secret, the key name needs to be the keystore key path. In this example we will create a secret from a file and from a literal string.

```
kubectl create secret generic encryption_key --from-file=xpack.watcher.encryption_key=./watcher_encryption_key
kubectl create secret generic slack_hook --from-literal=xpack.notification.slack.account.monitoring.secure_url='https://hooks.slack.com/services/asdasdasd/asdasdas/asdasd'
```

To add these secrets to the keystore:
```
keystore:
- secretName: encryption_key
- secretName: slack_hook
```

##### Multiple keys

All keys in the secret will be added to the keystore. To create the previous example in one secret you could also do:

```
kubectl create secret generic keystore_secrets --from-file=xpack.watcher.encryption_key=./watcher_encryption_key --from-literal=xpack.notification.slack.account.monitoring.secure_url='https://hooks.slack.com/services/asdasdasd/asdasdas/asdasd'
```

```
keystore:
- secretName: keystore_secrets
```

##### Custom paths and keys

If you are using these secrets for other applications (besides the Elasticsearch keystore) then it is also possible to specify the keystore path and which keys you want to add. Everything specified under each `keystore` item will be passed through to the `volumeMounts` section for [mounting the secret](https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets). In this example we will only add the `slack_hook` key from a secret that also has other keys. Our secret looks like this:

```
kubectl create secret generic slack_secrets --from-literal=slack_channel='#general' --from-literal=slack_hook='https://hooks.slack.com/services/asdasdasd/asdasdas/asdasd'
```

We only want to add the `slack_hook` key to the keystore at path `xpack.notification.slack.account.monitoring.secure_url`.

```
keystore:
- secretName: slack_secrets
items:
- key: slack_hook
path: xpack.notification.slack.account.monitoring.secure_url
```

You can also take a look at the [config example](/elasticsearch/examples/config/) which is used as part of the automated testing pipeline.

#### How to enable snapshotting?

Expand Down
19 changes: 19 additions & 0 deletions elasticsearch/examples/config/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
default: test
include ../../../helpers/examples.mk

RELEASE := helm-es-config

install:
helm upgrade --wait --timeout=600 --install $(RELEASE) --values ./values.yaml ../../ ; \

secrets:
kubectl delete secret elastic-config-credentials elastic-config-secret elastic-config-slack elastic-config-custom-path || true
kubectl create secret generic elastic-config-credentials --from-literal=password=changeme --from-literal=username=elastic
kubectl create secret generic elastic-config-slack --from-literal=xpack.notification.slack.account.monitoring.secure_url='https://hooks.slack.com/services/asdasdasd/asdasdas/asdasd'
kubectl create secret generic elastic-config-secret --from-file=xpack.watcher.encryption_key=./watcher_encryption_key
kubectl create secret generic elastic-config-custom-path --from-literal=slack_url='https://hooks.slack.com/services/asdasdasd/asdasdas/asdasd' --from-literal=thing_i_don_tcare_about=test

test: secrets install goss

purge:
helm del --purge $(RELEASE)
3 changes: 3 additions & 0 deletions elasticsearch/examples/config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Config

An example testing suite for testing some of the optional features of this chart.
26 changes: 26 additions & 0 deletions elasticsearch/examples/config/test/goss.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
http:
http://localhost:9200/_cluster/health:
status: 200
timeout: 2000
body:
- 'green'
- '"number_of_nodes":1'
- '"number_of_data_nodes":1'

http://localhost:9200:
status: 200
timeout: 2000
body:
- '"cluster_name" : "config"'
- '"name" : "config-master-0"'
- 'You Know, for Search'

command:
"elasticsearch-keystore list":
exit-status: 0
stdout:
- keystore.seed
- bootstrap.password
- xpack.notification.slack.account.monitoring.secure_url
- xpack.notification.slack.account.otheraccount.secure_url
- xpack.watcher.encryption_key
31 changes: 31 additions & 0 deletions elasticsearch/examples/config/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---

clusterName: "config"
replicas: 1

extraEnvs:
- name: ELASTIC_PASSWORD
valueFrom:
secretKeyRef:
name: elastic-credentials
key: password
- name: ELASTIC_USERNAME
valueFrom:
secretKeyRef:
name: elastic-credentials
key: username

# This is just a dummy file to make sure that
# the keystore can be mounted at the same time
# as a custom elasticsearch.yml
esConfig:
elasticsearch.yml: |
path.data: /usr/share/elasticsearch/data

keystore:
- secretName: elastic-config-secret
- secretName: elastic-config-slack
- secretName: elastic-config-custom-path
items:
- key: slack_url
path: xpack.notification.slack.account.otheraccount.secure_url
1 change: 1 addition & 0 deletions elasticsearch/examples/config/watcher_encryption_key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
supersecret
45 changes: 45 additions & 0 deletions elasticsearch/templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ spec:
configMap:
name: {{ template "uname" . }}-config
{{- end }}
{{- if .Values.keystore }}
- name: keystore
emptyDir: {}
{{- range .Values.keystore }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious, why not have this {{- range ...}} inside the {{ if ... block above? I I don't think there'd be any behavior changes, just curious about the style choice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a leftover from the original refactoring. But you are right that it doesn't change anything. I fixed it up to make it as explicit as possible.

- name: keystore-{{ .secretName }}
secret: {{ toYaml . | nindent 12 }}
{{- end }}
{{ end }}
{{- if .Values.extraVolumes }}
{{ tpl .Values.extraVolumes . | indent 8 }}
{{- end }}
Expand All @@ -129,6 +137,38 @@ spec:
resources:
{{ toYaml .Values.initResources | indent 10 }}
{{- end }}
{{ if .Values.keystore }}
- name: keystore
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
command:
- sh
- -c
- |
#!/usr/bin/env bash
set -euo pipefail

elasticsearch-keystore create

for i in /tmp/keystoreSecrets/*/*; do
key=$(basename $i)
echo "Adding file $i to keystore key $key"
elasticsearch-keystore add-file "$key" "$i"
done

# Add the bootstrap password since otherwise the Elasticsearch entrypoint tries to do this on startup
[ ! -z "$ELASTIC_PASSWORD" ] && echo "$ELASTIC_PASSWORD" | elasticsearch-keystore add -x bootstrap.password

cp -a /usr/share/elasticsearch/config/elasticsearch.keystore /tmp/keystore/
env: {{ toYaml .Values.extraEnvs | nindent 10 }}
resources: {{ toYaml .Values.initResources | nindent 10 }}
volumeMounts:
- name: keystore
mountPath: /tmp/keystore
{{- range .Values.keystore }}
- name: keystore-{{ .secretName }}
mountPath: /tmp/keystoreSecrets/{{ .secretName }}
{{- end }}
{{ end }}
{{- if .Values.extraInitContainers }}
{{ tpl .Values.extraInitContainers . | indent 6 }}
{{- end }}
Expand Down Expand Up @@ -219,6 +259,11 @@ spec:
- name: "{{ template "uname" . }}"
mountPath: /usr/share/elasticsearch/data
{{- end }}
{{ if .Values.keystore }}
- name: keystore
mountPath: /usr/share/elasticsearch/config/elasticsearch.keystore
subPath: elasticsearch.keystore
{{ end }}
{{- range .Values.secretMounts }}
- name: {{ .name }}
mountPath: {{ .path }}
Expand Down
106 changes: 106 additions & 0 deletions elasticsearch/tests/elasticsearch_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,3 +793,109 @@ def test_adding_pod_labels():
'''
r = helm_template(config)
assert r['statefulset'][uname]['metadata']['labels']['app.kubernetes.io/name'] == 'elasticsearch'

def test_keystore_enable():
config = ''

r = helm_template(config)
s = r['statefulset'][uname]['spec']['template']['spec']

assert s['volumes'] == None

config = '''
keystore:
- secretName: test
'''

r = helm_template(config)
s = r['statefulset'][uname]['spec']['template']['spec']

assert {'name': 'keystore', 'emptyDir': {}} in s['volumes']

def test_keystore_init_container():
config = ''

r = helm_template(config)
i = r['statefulset'][uname]['spec']['template']['spec']['initContainers'][-1]

assert i['name'] != 'keystore'

config = '''
keystore:
- secretName: test
'''

r = helm_template(config)
i = r['statefulset'][uname]['spec']['template']['spec']['initContainers'][-1]

assert i['name'] == 'keystore'

def test_keystore_mount():
config = '''
keystore:
- secretName: test
'''

r = helm_template(config)
s = r['statefulset'][uname]['spec']['template']['spec']
assert s['containers'][0]['volumeMounts'][-1] == {
'mountPath': '/usr/share/elasticsearch/config/elasticsearch.keystore',
'subPath': 'elasticsearch.keystore',
'name': 'keystore'
}

def test_keystore_init_volume_mounts():
config = '''
keystore:
- secretName: test
- secretName: test-with-custom-path
items:
- key: slack_url
path: xpack.notification.slack.account.otheraccount.secure_url
'''
r = helm_template(config)
s = r['statefulset'][uname]['spec']['template']['spec']
assert s['initContainers'][-1]['volumeMounts'] == [
{
'mountPath': '/tmp/keystore',
'name': 'keystore'
},
{
'mountPath': '/tmp/keystoreSecrets/test',
'name': 'keystore-test'
},
{
'mountPath': '/tmp/keystoreSecrets/test-with-custom-path',
'name': 'keystore-test-with-custom-path'
}
]

def test_keystore_volumes():
config = '''
keystore:
- secretName: test
- secretName: test-with-custom-path
items:
- key: slack_url
path: xpack.notification.slack.account.otheraccount.secure_url
'''
r = helm_template(config)
s = r['statefulset'][uname]['spec']['template']['spec']

assert {
'name': 'keystore-test',
'secret': {
'secretName': 'test'
}
} in s['volumes']

assert {
'name': 'keystore-test-with-custom-path',
'secret': {
'secretName': 'test-with-custom-path',
'items': [{
'key': 'slack_url',
'path': 'xpack.notification.slack.account.otheraccount.secure_url'
}]
}
} in s['volumes']
2 changes: 2 additions & 0 deletions elasticsearch/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,5 @@ lifecycle: {}

sysctlInitContainer:
enabled: true

keystore: []
1 change: 1 addition & 0 deletions helpers/matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CHART:
- metricbeat
ES_SUITE:
- default
- config
- multi
- oss
- security
Expand Down