Skip to content

Commit

Permalink
make Elasticsearch indexing work. #15, fixes #12.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredjennings committed May 21, 2021
1 parent 2db19d3 commit 7d959b9
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 49 deletions.
110 changes: 87 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ It's early days, but if you want to try it out, clone this repository,
cd into it, and

```
helm install .
helm install my-thehive .
```

You'll need to customize the values.yaml or provide some `--set`
command line options, of course. On my single-node home k3s 1.20.2
cluster with stock Traefik 1.7 and Helm 3.5.2, this does the trick for
me:
command line options, of course.


### Smallest install: local database, index and attachments

On my single-node home k3s 1.20.2 cluster with stock Traefik 1.7 and
Helm 3.5.2, this does the trick for me:

```
helm install -n thehive my-thehive . \
Expand All @@ -26,13 +30,69 @@ helm install -n thehive my-thehive . \
```

Defaults are for local index storage, local database storage, and
attachment storage, all on Kubernetes persistent volumes.
attachment storage, all on Kubernetes persistent volumes with the
default access modes (`ReadWriteOnce`). The `storageClass` you need to
use will vary with your Kubernetes distribution and its configuration.

Now, this kind of install is not the best use of this Helm
chart. Clearly you cannot scale past a single replica of TheHive
because of the local storage; but the chart sets up a Kubernetes
Deployment for TheHive, which expects in the face of any change to
stand up the new TheHive Pod before taking down the old one. But the
new one fails to become ready because the old one has files locked. To
work around this, before changing anything (`helm upgrade` for
example) or deleting a Pod, you must stand down the one you have:

## Caveats
```
kubectl scale --replicas=0 deployment my-thehive
```

### Scalable data: Cassandra and ECK Elasticsearch

This is the same as the above, but storing data in Cassandra and
indexing using Elasticsearch. Make a `values-as-given.yaml`:

```yaml
ingress:
hosts:
- host: h2.k.my.dns.domain
paths:
- path: /
storageClass: local-path
elasticsearch:
eck:
enabled: true
name: my-eck-cluster
cassandra:
enabled: true
persistence:
storageClass: local-path
dbUser:
password: "my-super-secure-password"
```
#### Idempotence
You should specify a value for `cassandra.dbUser.password`. When the
Cassandra chart sets up Cassandra for the first time, it sets the
password as directed, defaulting to a random password. The password is
then saved in a Secret. Now if you `helm upgrade`, say, to change some
of your values, the Cassandra chart will make a new Secret, but the
persistent volume(s) where the password was set during initial setup -
and where the data lives! - won't get deleted. If you specified a
password, this all works fine; but if you let it default to something
random, the Secret will now contain a wrong password, and anything
obtaining the password from that Secret (e.g. TheHive as configured by
this chart) will fail to access Cassandra. So set the password. You
should change it periodically, but that task doesn't appear to be
handled for you by these charts.

#### Caveats

Upon first installation, TheHive may fail to connect to Cassandra for
a few minutes. Try waiting it out.


## Improving it

If this chart doesn't flex in a way you need it to, and there isn't
Expand Down Expand Up @@ -90,26 +150,30 @@ specify the storageClass for each independently as needed.

## Elasticsearch

| Parameter | Description | Default |
| --------- | ----------- | ------- |
| elasticsearch.eck.enabled | Set this to true if you used ECK to set up an Elasticsearch cluster. | false |
| elasticsearch.eck.name | Set to the name of the `Elasticsearch` custom resource. | nil |
| elasticsearch.external.enabled | Set this to true if you have a non-ECK Elasticsearch server/cluster. | false |
| elasticsearch.username | Username with which to authenticate to Elasticsearch. | elastic<sup>1,2</sup> |
| elasticsearch.userSecret | Secret containing the password for the named user. | nil<sup>1</sup> |
| elasticsearch.url | URL to Elasticsearch server/cluster. | nil<sup>1</sup> |
| elasticsearch.tls | Set this to true to provide a CA cert to trust. | true<sup>1</sup> |
| elasticsearch.caCertSecret | Secret containing the CA certificate to trust. | nil<sup>1,3</sup> |
| elasticsearch.caCert | PEM text of the CA cert to trust. | nil<sup>1,3</sup> |
| Parameter | Description | Default |
| --------- | ----------- | ------- |
| elasticsearch.eck.enabled | Set this to true if you used ECK to set up an Elasticsearch cluster. | false |
| elasticsearch.eck.name | Set to the name of the `Elasticsearch` custom resource. | nil |
| elasticsearch.external.enabled | Set this to true if you have a non-ECK Elasticsearch server/cluster. | false |
| elasticsearch.username | Username with which to authenticate to Elasticsearch. | elastic<sup>1,2</sup> |
| elasticsearch.userSecret | Secret containing the password for the named user. | nil<sup>1</sup> |
| elasticsearch.url | URL to Elasticsearch server/cluster. | nil<sup>1</sup> |
| elasticsearch.tls | Set this to true to provide a CA cert to trust. | true<sup>1</sup> |
| elasticsearch.caCertSecret | Secret containing the CA certificate to trust. | nil<sup>1,3</sup> |
| elasticsearch.caCertSecretMappingKey | Name of the key in the caCertSecret whose value is the CA certificate. | "ca.crt"<sup>1,3</sup> |
| elasticsearch.caCert | PEM text of the CA cert to trust. | nil<sup>1,3</sup> |

Notes:

1. If you use ECK to set up an Elasticsearch cluster, you don't need to specify this.
2. The user secret should be an opaque secret, with data whose key is the username and value is the password.
3. The CA cert secret should be an opaque secret with data whose key
is 'tls.crt' and value is the PEM-encoded certificate. If you don't
have such a secret already, provide the PEM-encoded certificate as
the value of `elasticsearch.caCert` and the secret will be
1. If you use ECK to set up an Elasticsearch cluster, you don't need
to specify this.
2. The user secret should be an opaque secret, with data whose key is
the username and value is the password.
3. The `caCertSecret` should be an opaque secret with a key named by
`caCertSecretMappingKey` whose value is the PEM-encoded
certificate. It could have other keys and values. If you don't have
such a secret already, you can provide the PEM-encoded certificate
itself as the `elasticsearch.caCert` value, and the secret will be
constructed for you.


Expand Down
20 changes: 18 additions & 2 deletions templates/_cacerts.tpl
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
{{- define "thehive.esCACertDir" -}}
/tmp/es-http-ca
{{- end }}


{{- define "thehive.esCACert" -}}
{{ printf "%s/ca.crt" (include "thehive.esCACertDir" .) }}
{{- end }}


{{- define "thehive.esCACertVolumes" -}}
{{- if .Values.elasticsearch.tls }}
- name: es-http-ca
secret:
secretName: {{ default .Values.elasticsearch.caCertSecret (include "thehive.elasticCACertSecretName" .) }}
secretName: {{ .Values.elasticsearch.caCertSecret | default (include "thehive.elasticCACertSecretName" .) }}
items:
- key: {{ .Values.elasticsearch.caCertSecretMappingKey | quote }}
path: "ca.crt"
{{- end }}
{{- end }}


{{- define "thehive.esCACertVolumeMounts" -}}
- name: es-http-ca
mountPath: {{ include "thehive.esCACertDir" . | quote }}
Expand All @@ -28,11 +34,21 @@ trust store can serve as an empty keystore too. This will need to be
changed if Elasticsearch client certs are ever supported.
*/}}
{{- define "thehive.esTrustStoreDir" -}}
/etc/cortex/es-trust
/etc/thehive/es-trust
{{- end }}


{{- define "thehive.esTrustStore" -}}
{{ printf "%s/store" (include "thehive.esTrustStoreDir" .) }}
{{- end }}


{{- define "thehive.esTrustStoreVolume" -}}
- name: es-trust-store
emptyDir: {}
{{- end }}


{{- define "thehive.esTrustStoreVolumeMount" -}}
- name: es-trust-store
mountPath: {{ include "thehive.esTrustStoreDir" . }}
Expand Down
19 changes: 11 additions & 8 deletions templates/_elasticsearch.tpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{- define "thehive.elasticUserSecretName" -}}
{{- if .Values.elasticsearch.eck.enabled -}}
{{- if .Values.elasticsearch.eck.name -}}
{{ printf "%s-%s" .Values.elasticsearch.eck.name "es-elastic-user" | quote }}
{{ printf "%s-%s" .Values.elasticsearch.eck.name "es-elastic-user" }}
{{- else -}}
{{ fail "While trying to construct user secret name: when elasticsearch.eck.enabled is true, you must provide elasticsearch.eck.name." }}
{{- end -}}
Expand All @@ -16,23 +16,26 @@
{{- end -}}
{{- end }}

{{- define "thehive.elasticURL" -}}

{{- define "thehive.elasticHostname" -}}
{{- if .Values.elasticsearch.eck.enabled -}}
{{ printf "https://%s-es-http:9200" .Values.elasticsearch.eck.name | quote }}
{{- else -}}{{- /* guess */ -}}
"https://elasticsearch:9200"
{{ printf "%s-es-http" .Values.elasticsearch.eck.name }}
{{- else -}}
{{ .Values.elasticsearch.hostname | default "elasticsearch" }}
{{- end -}}
{{- end }}


{{- define "thehive.elasticCACertSecretName" -}}
{{- if .Values.elasticsearch.eck.enabled -}}
{{- if .Values.elasticsearch.eck.name -}}
{{ printf "%s-%s" (.Values.elasticsearch.eck.name) "es-http-ca-internal" | quote }}
{{ printf "%s-%s" (.Values.elasticsearch.eck.name) "es-http-certs-public" }}
{{- else -}}
{{ fail "CA cert secret: when ECK is enabled you must supply a value for elasticsearch.eck.name." }}
{{- end -}}
{{- else if .Values.elasticsearch.external.enabled -}}
{{ printf "%s-%s" (include "thehive.fullname" .) "external-es-http-ca" | quote }}
{{- else -}}{{- /* with TheHive it is ok to not use Elasticsearch */ -}}
{{- else -}}
{{ fail "Elastic CA cert summoned, but Elastic support is not enabled??" }}
{{- end -}}
{{- end }}
{{- end }}
28 changes: 27 additions & 1 deletion templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,33 @@ data:
}
index {
{{- if (or .Values.elasticsearch.eck.enabled .Values.elasticsearch.external.enabled) }}
{{ fail "In main.conf.tmpl, elasticsearch index config unimplemented by chart" }}
search {
backend: elasticsearch
hostname: [ {{ include "thehive.elasticHostname" . | quote }} ]
index-name: thehive
elasticsearch {
http {
auth {
type: basic
basic {
username: "@@ES_USERNAME@@"
password: "@@ES_PASSWORD@@"
}
}
}
{{- if .Values.elasticsearch.tls }}
ssl {
enabled: true
truststore {
location: {{ include "thehive.esTrustStore" . | quote }}
# There are no private keys to protect in this trust
# store, so its password need not actually secure it.
password: changeit
}
}
{{- end }}
}
}
{{- else if .Values.localIndexStorage.pvc.enabled }}
search {
backend = lucene
Expand Down
9 changes: 4 additions & 5 deletions templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ spec:
{{- if or .Values.elasticsearch.eck.enabled .Values.elasticsearch.external.enabled }}
{{- if .Values.elasticsearch.tls }}
{{- include "thehive.esCACertVolumes" . | nindent 8 }}
- name: es-trust-store
emptyDir: {}
{{- include "thehive.esTrustStoreVolume" . | nindent 8 }}
{{- end }}
{{- else }}
{{- if .Values.localIndexStorage.pvc.enabled }}
Expand Down Expand Up @@ -150,7 +149,8 @@ spec:
- sh
- -c
- |
keytool -importcert -keystore {{ include "thehive.esTrustStore" . | quote }} \
keytool -importcert \
-keystore {{ include "thehive.esTrustStore" . | quote }} \
-file {{ include "thehive.esCACert" . | quote }} \
-alias es-http-ca \
-storepass "$ES_TRUST_STORE_PASSWORD" \
Expand Down Expand Up @@ -202,8 +202,7 @@ spec:
{{- end }}
{{- if (or .Values.elasticsearch.eck.enabled .Values.elasticsearch.external.enabled) }}
{{- if .Values.elasticsearch.tls }}
- name: es-http-ca
mountPath: /opt/thehive/es-http-ca
{{ include "thehive.esTrustStoreVolumeMount" . | nindent 12 }}
{{- end }}
{{- else }}
{{- if .Values.localIndexStorage.pvc.enabled }}
Expand Down
17 changes: 17 additions & 0 deletions templates/external-es-http-ca-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{{- if .Values.elasticsearch.external.enabled }}
{{- if .Values.elasticsearch.tls }}
{{- if .Values.elasticsearch.caCertSecret }}
{{- else }}
apiVersion: v1
kind: Secret
metadata:
namespace: {{ .Release.Namespace }}
name: {{ include "thehive.elasticCACertSecretName" . }}
labels:
{{- include "thehive.labels" . | nindent 4 }}
type: Opaque
stringData:
{{ .Values.elasticsearch.caCertSecretKey | quote }}: {{ .Values.elasticsearch.caCert | quote }}
{{- end }}
{{- end }}
{{- end }}
29 changes: 19 additions & 10 deletions values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,23 +105,32 @@ elasticsearch:
# is enabled, the default is the secret created by ECK. If ECK is
# not enabled, this must be set.
userSecret: ""
# Where to reach the Elasticsearch cluster. Probably
# "https://something:9200". If ECK is enabled, the default is the
# service created by ECK.
url: ""
# The name of the Elasticsearch host to connect to. If ECK is
# enabled, the ECK service is used. If ECK is not enabled, this must
# be set.
hostname: ""
# If true, this chart expects to tell Cortex a CA cert to trust when
# connecting to Elasticsearch. If ECK is enabled, this has to be set
# to true. If ECK is not enabled and this is set to true, you need
# to provide a CA certificate using the caCertSecret or caCert
# values.
tls: true
# A CA cert to trust when connecting to Elasticsearch using
# HTTPS. The secret should contain a key "tls.crt" whose value is
# the PEM-encoded cert. If ECK is enabled, the default is the
# appropriate secret created by ECK. If ECK is not enabled, and this
# is not set, a secret will be created using the `caCert` value
# below.
# The name of a Kubernetes Secret object containing a CA cert to
# trust when connecting to Elasticsearch using HTTPS. The secret
# should contain a mapping with a key, named by
# `caCertSecretMappingKey`, whose value is the PEM-encoded cert. If
# ECK is enabled, the default is the appropriate secret created by
# ECK. If ECK is not enabled, and this is not set, a Secret will be
# created using the `caCert` value below. N.B. despite the
# juxtaposition of the words `caCert` and `Secret`, the private key
# of the certificate authority is far from what we are talking about
# here.
caCertSecret: ""
# The name of the key inside the caCertSecret, whose value is the
# PEM-encoded cert. N.B. despite the juxtaposition of the words
# `caCert` and `Secret`, the private key of the certificate
# authority is far from what we are talking about here.
caCertSecretMappingKey: "ca.crt"
# A secret is created and used for CA cert trusting, using a
# PEM-encoded cert from this value, if ECK is not enabled and
# caCertSecret is not set.
Expand Down

0 comments on commit 7d959b9

Please sign in to comment.