Skip to content

Commit 0d09f4c

Browse files
authored
Fix remote ES client to use ssl.* settings (#3522)
1 parent e4d1e2f commit 0d09f4c

File tree

6 files changed

+125
-36
lines changed

6 files changed

+125
-36
lines changed

Makefile

+3-2
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ int-docker-start-async:
305305
.PHONY: int-docker-wait
306306
int-docker-wait:
307307
@./dev-tools/integration/wait-for-elasticsearch.sh ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD}@${TEST_ELASTICSEARCH_HOSTS}
308-
@./dev-tools/integration/wait-for-elasticsearch.sh ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD}@${TEST_REMOTE_ELASTICSEARCH_HOST}
308+
@./dev-tools/integration/wait-for-elasticsearch.sh https://${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD}@${TEST_REMOTE_ELASTICSEARCH_HOST}
309309

310310
# Start integration docker setup with wait for when the ES is ready
311311
.PHONY: int-docker-start
@@ -335,7 +335,8 @@ test-int: prepare-test-context ## - Run integration tests with full setup (slow
335335
test-int-set: ## - Run integration tests without setup
336336
# Initialize indices one before running all the tests
337337
ELASTICSEARCH_SERVICE_TOKEN=$(shell ./dev-tools/integration/get-elasticsearch-servicetoken.sh ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD}@${TEST_ELASTICSEARCH_HOSTS} "fleet-server") \
338-
REMOTE_ELASTICSEARCH_SERVICE_TOKEN=$(shell ./dev-tools/integration/get-elasticsearch-servicetoken.sh ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD}@${TEST_REMOTE_ELASTICSEARCH_HOST} "fleet-server-remote") \
338+
REMOTE_ELASTICSEARCH_SERVICE_TOKEN=$(shell ./dev-tools/integration/get-elasticsearch-servicetoken.sh https://${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD}@${TEST_REMOTE_ELASTICSEARCH_HOST} "fleet-server-remote") \
339+
REMOTE_ELASTICSEARCH_CA_CRT_BASE64="$(shell COMPOSE_PROJECT_NAME=integration docker compose -f ./dev-tools/e2e/docker-compose.yml --env-file ./dev-tools/integration/.env exec elasticsearch-remote /bin/bash -c "cat /usr/share/elasticsearch/config/certs/ca/ca.crt" | base64)" \
339340
ELASTICSEARCH_HOSTS=${TEST_ELASTICSEARCH_HOSTS} ELASTICSEARCH_USERNAME=${ELASTICSEARCH_USERNAME} ELASTICSEARCH_PASSWORD=${ELASTICSEARCH_PASSWORD} \
340341
go test -v -tags=integration -count=1 -race -p 1 ./...
341342

dev-tools/integration/docker-compose.yml

+70
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,58 @@
11

22
version: '2.3'
3+
volumes:
4+
certs:
5+
driver: local
6+
37
services:
8+
setup:
9+
image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTICSEARCH_VERSION}-amd64
10+
volumes:
11+
- certs:/usr/share/elasticsearch/config/certs
12+
user: "0"
13+
healthcheck:
14+
test: ["CMD-SHELL", "[ -f config/certs/es01/es01.crt ]"]
15+
interval: 1s
16+
timeout: 5s
17+
retries: 120
18+
command: >
19+
bash -c '
20+
if [ ! -f config/certs/ca.zip ]; then
21+
echo "Creating CA";
22+
bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip;
23+
unzip config/certs/ca.zip -d config/certs;
24+
fi;
25+
if [ ! -f config/certs/certs.zip ]; then
26+
echo "Creating certs";
27+
echo -ne \
28+
"instances:\n"\
29+
" - name: es01\n"\
30+
" dns:\n"\
31+
" - es01\n"\
32+
" - localhost\n"\
33+
" ip:\n"\
34+
" - 127.0.0.1\n"\
35+
" - name: es02\n"\
36+
" dns:\n"\
37+
" - es02\n"\
38+
" - localhost\n"\
39+
" ip:\n"\
40+
" - 127.0.0.1\n"\
41+
" - name: es03\n"\
42+
" dns:\n"\
43+
" - es03\n"\
44+
" - localhost\n"\
45+
" ip:\n"\
46+
" - 127.0.0.1\n"\
47+
> config/certs/instances.yml;
48+
bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key;
49+
unzip config/certs/certs.zip -d config/certs;
50+
fi;
51+
echo "Setting file permissions"
52+
chown -R root:root config/certs;
53+
find . -type d -exec chmod 750 \{\} \;;
54+
find . -type f -exec chmod 640 \{\} \;;
55+
';
456
elasticsearch:
557
image: "docker.elastic.co/elasticsearch/elasticsearch:${ELASTICSEARCH_VERSION}-amd64"
658
container_name: elasticsearch
@@ -25,13 +77,30 @@ services:
2577
- 127.0.0.1:9200:9200
2678

2779
elasticsearch-remote:
80+
depends_on:
81+
setup:
82+
condition: service_healthy
2883
image: "docker.elastic.co/elasticsearch/elasticsearch:${ELASTICSEARCH_VERSION}-amd64"
2984
container_name: elasticsearch-remote
3085
environment:
3186
- node.name=es02
3287
- cluster.name=es-docker-cluster2
3388
- discovery.seed_hosts=elasticsearch
3489
- bootstrap.memory_lock=true
90+
- xpack.security.enabled=true
91+
- xpack.security.http.ssl.enabled=true
92+
- xpack.security.http.ssl.key=certs/es02/es02.key
93+
- xpack.security.http.ssl.certificate=certs/es02/es02.crt
94+
- xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
95+
- xpack.security.transport.ssl.enabled=true
96+
- xpack.security.transport.ssl.key=certs/es02/es02.key
97+
- xpack.security.transport.ssl.certificate=certs/es02/es02.crt
98+
- xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt
99+
- xpack.security.transport.ssl.verification_mode=certificate
100+
- cluster.name="docker-cluster"
101+
- network.host="0.0.0.0"
102+
- xpack.security.authc.api_key.enabled="true"
103+
- xpack.license.self_generated.type="trial"
35104
- "ES_JAVA_OPTS=-Xms1G -Xmx1G"
36105
- "ELASTIC_USERNAME=${ELASTICSEARCH_USERNAME}"
37106
- "ELASTIC_PASSWORD=${ELASTICSEARCH_PASSWORD}"
@@ -43,6 +112,7 @@ services:
43112
soft: 65536
44113
hard: 65536
45114
volumes:
115+
- certs:/usr/share/elasticsearch/config/certs
46116
- ./elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
47117
ports:
48118
- 127.0.0.1:9201:9200

dev-tools/integration/get-elasticsearch-servicetoken.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
host="$1"
44
account="$2"
55

6-
jsonBody="$(curl -sSL -XPOST "$host/_security/service/elastic/$account/credential/token/token1")"
6+
jsonBody="$(curl --insecure -sSL -XPOST "$host/_security/service/elastic/$account/credential/token/token1")"
77

88
# use grep and sed to get the service token value as we may not have jq or a similar tool on the instance
99
token=$(echo ${jsonBody} | grep -Eo '"value"[^}]*' | grep -Eo ':.*' | sed -r "s/://" | sed -r 's/"//g')

dev-tools/integration/wait-for-elasticsearch.sh

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,27 @@ shift
77
cmd="$@"
88

99

10-
until $(curl --output /dev/null --silent --head --fail "$host"); do
10+
until $(curl --insecure --output /dev/null --silent --head --fail "$host"); do
1111
printf '.'
1212
sleep 1
1313
done
1414

1515
# First wait for ES to start...
16-
response=$(curl $host)
16+
response=$(curl --insecure $host)
1717

1818
until [ "$response" = "200" ]; do
19-
response=$(curl --write-out %{http_code} --silent --output /dev/null "$host")
19+
response=$(curl --insecure --write-out %{http_code} --silent --output /dev/null "$host")
2020
echo '.'
2121
sleep 1
2222
done
2323

2424

2525
# next wait for ES status to turn to green
26-
health="$(curl -fsSL "$host/_cat/health?h=status")"
26+
health="$(curl --insecure -fsSL "$host/_cat/health?h=status")"
2727
health="$(echo "$health" | tr -d '[:space:]')"
2828

2929
until [ "$health" = 'green' -o "$health" = 'yellow' ]; do
30-
health="$(curl -fsSL "$host/_cat/health?h=status")"
30+
health="$(curl --insecure -fsSL "$host/_cat/health?h=status")"
3131
echo $health
3232
health="$(echo "$health" | tr -d '[:space:]')"
3333
>&2 echo "Elasticsearch is unavailable - sleeping"

internal/pkg/bulk/engine.go

+13-17
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/elastic/fleet-server/v7/internal/pkg/config"
2020
"github.com/elastic/fleet-server/v7/internal/pkg/es"
2121
"github.com/elastic/fleet-server/v7/internal/pkg/logger"
22+
"github.com/elastic/go-ucfg"
2223

2324
"github.com/elastic/go-elasticsearch/v8"
2425
"github.com/elastic/go-elasticsearch/v8/esapi"
@@ -198,29 +199,24 @@ func (b *Bulker) CreateAndGetBulker(ctx context.Context, zlog zerolog.Logger, ou
198199
var newESClient = es.NewClient
199200

200201
func (b *Bulker) createRemoteEsClient(ctx context.Context, outputName string, outputMap map[string]map[string]interface{}) (*elasticsearch.Client, error) {
201-
hostsObj := outputMap[outputName]["hosts"]
202-
hosts, ok := hostsObj.([]interface{})
203-
if !ok {
204-
return nil, fmt.Errorf("failed to get hosts from output: %v", hostsObj)
202+
var esOutput config.Elasticsearch
203+
esConfig, err := ucfg.NewFrom(outputMap[outputName], config.DefaultOptions...)
204+
if err != nil {
205+
return nil, err
205206
}
206-
hostsStrings := make([]string, len(hosts))
207-
for i, host := range hosts {
208-
hostsStrings[i], ok = host.(string)
209-
if !ok {
210-
return nil, fmt.Errorf("failed to get hosts from output: %v", host)
211-
}
207+
err = esConfig.Unpack(&esOutput)
208+
if err != nil {
209+
return nil, err
212210
}
213-
serviceToken, ok := outputMap[outputName]["service_token"].(string)
214-
if !ok {
211+
if len(esOutput.Hosts) == 0 {
212+
return nil, fmt.Errorf("failed to get hosts from output: %v", outputName)
213+
}
214+
if esOutput.ServiceToken == "" {
215215
return nil, fmt.Errorf("failed to get service token from output: %v", outputName)
216216
}
217-
218217
cfg := config.Config{
219218
Output: config.Output{
220-
Elasticsearch: config.Elasticsearch{
221-
Hosts: hostsStrings,
222-
ServiceToken: serviceToken,
223-
},
219+
Elasticsearch: esOutput,
224220
},
225221
}
226222
es, err := newESClient(ctx, &cfg, false, elasticsearchOptions(

internal/pkg/server/remote_es_output_integration_test.go

+33-11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ package server
88

99
import (
1010
"context"
11+
"crypto/tls"
12+
"encoding/base64"
1113
"encoding/json"
1214
"fmt"
1315
"io"
@@ -28,6 +30,7 @@ import (
2830

2931
const (
3032
remoteESHost = "localhost:9201"
33+
remoteESUrl = "https://localhost:9201"
3134
)
3235

3336
func Checkin(t *testing.T, ctx context.Context, srv *tserver, agentID, key string, shouldHaveRemoteES bool, actionType string) (string, string) {
@@ -96,6 +99,13 @@ func Checkin(t *testing.T, ctx context.Context, srv *tserver, agentID, key strin
9699
return remoteAPIKey, actionID
97100
}
98101

102+
func getRemoteElasticsearchCa(t *testing.T) string {
103+
data, err := base64.StdEncoding.DecodeString(strings.Replace(os.Getenv("REMOTE_ELASTICSEARCH_CA_CRT_BASE64"), " ", "", -1))
104+
require.NoError(t, err)
105+
106+
return string(data)
107+
}
108+
99109
func Ack(t *testing.T, ctx context.Context, srv *tserver, actionID, agentID, key string) {
100110
t.Logf("Fake an ack for action %s for agent %s", actionID, agentID)
101111
body := fmt.Sprintf(`{
@@ -146,9 +156,11 @@ func Test_Agent_Remote_ES_Output(t *testing.T) {
146156
"type": "elasticsearch",
147157
},
148158
"remoteES": {
149-
"type": "remote_elasticsearch",
150-
"hosts": []string{remoteESHost},
151-
"service_token": os.Getenv("REMOTE_ELASTICSEARCH_SERVICE_TOKEN"),
159+
"type": "remote_elasticsearch",
160+
"hosts": []string{remoteESUrl},
161+
"service_token": os.Getenv("REMOTE_ELASTICSEARCH_SERVICE_TOKEN"),
162+
"ssl.enabled": true,
163+
"ssl.certificate_authorities": []string{getRemoteElasticsearchCa(t)},
152164
},
153165
},
154166
OutputPermissions: json.RawMessage(`{"default": {}, "remoteES": {}}`),
@@ -255,13 +267,19 @@ func verifyRemoteAPIKey(t *testing.T, ctx context.Context, apiKeyID string, inva
255267
// need to wait a bit before querying the api key
256268
time.Sleep(time.Second)
257269

258-
requestURL := fmt.Sprintf("http://elastic:changeme@%s/_security/api_key?id=%s", remoteESHost, apiKeyID)
270+
requestURL := fmt.Sprintf("https://elastic:changeme@%s/_security/api_key?id=%s", remoteESHost, apiKeyID)
259271

260272
req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
273+
// Skip SSL verify as ES use self-signed certificate
274+
tr := &http.Transport{
275+
// #nosec G402
276+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
277+
}
278+
client := &http.Client{Transport: tr}
261279
if err != nil {
262280
t.Fatal("error creating request for remote api key")
263281
}
264-
res, err := http.DefaultClient.Do(req)
282+
res, err := client.Do(req)
265283
if err != nil {
266284
t.Fatal("error querying remote api key")
267285
}
@@ -292,9 +310,11 @@ func Test_Agent_Remote_ES_Output_ForceUnenroll(t *testing.T) {
292310
"type": "elasticsearch",
293311
},
294312
"remoteES": {
295-
"type": "remote_elasticsearch",
296-
"hosts": []string{remoteESHost},
297-
"service_token": os.Getenv("REMOTE_ELASTICSEARCH_SERVICE_TOKEN"),
313+
"type": "remote_elasticsearch",
314+
"hosts": []string{remoteESUrl},
315+
"service_token": os.Getenv("REMOTE_ELASTICSEARCH_SERVICE_TOKEN"),
316+
"ssl.enabled": true,
317+
"ssl.certificate_authorities": []string{getRemoteElasticsearchCa(t)},
298318
},
299319
},
300320
OutputPermissions: json.RawMessage(`{"default": {}, "remoteES": {}}`),
@@ -411,9 +431,11 @@ func Test_Agent_Remote_ES_Output_Unenroll(t *testing.T) {
411431
"type": "elasticsearch",
412432
},
413433
"remoteES": {
414-
"type": "remote_elasticsearch",
415-
"hosts": []string{remoteESHost},
416-
"service_token": os.Getenv("REMOTE_ELASTICSEARCH_SERVICE_TOKEN"),
434+
"type": "remote_elasticsearch",
435+
"hosts": []string{remoteESUrl},
436+
"service_token": os.Getenv("REMOTE_ELASTICSEARCH_SERVICE_TOKEN"),
437+
"ssl.enabled": true,
438+
"ssl.certificate_authorities": []string{getRemoteElasticsearchCa(t)},
417439
},
418440
},
419441
OutputPermissions: json.RawMessage(`{"default": {}, "remoteES": {}}`),

0 commit comments

Comments
 (0)