Skip to content

Commit 94869d9

Browse files
committed
WIP: Idea for how to handle multipe container configs for acceptance tests
This commit shows the rough structure for how I am planning on handling docker compose networks for acceptance tests. The main idea is to use interpolation in the docker compose file to point to different configuration files for filebeat/logstash/elasticsearch. This is mainly due to the nature of these tests showing behavior when the system is and is not configured properly for FIPS. The breakdown in responsibility is: 1. Gradle handles cert generation (similar to smoke test, this avoids checking in PKI) 2. Rspec handles stopping/starting docker compose and managing environment vars for intperolation in docker compose manifests (different from smoke tests where a single static docker compose is started in gradle) 3. Rspec handles deciding when containers are ready and querying state about data flowing through the system 4. Gradle cleans up certs THis is just a rough sketch, there are still bugs to be worked out but before i get too far in to it I want to get the idea out there.
1 parent 1642211 commit 94869d9

File tree

11 files changed

+259
-1
lines changed

11 files changed

+259
-1
lines changed

x-pack/build.gradle

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,20 @@ tasks.register("observabilitySREacceptanceTests", Test) {
120120
description = "Run ObservabilitySRE acceptance tests"
121121
// Need to have set up the ruby environment for rspec even through we are running in container
122122
dependsOn(":bootstrap", ":logstash-core:assemble", ":installDevelopmentGems")
123-
// TODO: hook in to rspec
123+
124+
inputs.files fileTree("${projectDir}/distributions/internal/observabilitySRE/qa/smoke")
125+
doFirst {
126+
// Generate the certificates first
127+
exec {
128+
workingDir file("distributions/internal/observabilitySRE/qa/acceptance/docker/certs")
129+
commandLine 'bash', './generate.sh'
130+
ignoreExitValue = false
131+
}
132+
}
133+
systemProperty 'logstash.root.dir', projectDir.parent
134+
include '**/org/logstash/xpack/test/RSpecObservabilitySREAcceptanceTests.class'
135+
doLast {
136+
// Clean up the generated certificates
137+
delete fileTree("distributions/internal/observabilitySRE/qa/acceptance/docker/certs").include("*.key", "*.crt", "*.csr", "*.srl")
138+
}
124139
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.crt
2+
*.csr
3+
*.key
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
3+
echo "Generating CA certificate"
4+
openssl req -x509 -newkey rsa:3072 -days 365 -nodes -keyout ca.key -out ca.crt -subj "/CN=Elastic-CA" -sha256
5+
6+
echo "Generating Elasticsearch certificate"
7+
openssl req -newkey rsa:3072 -nodes -keyout elasticsearch.key -out elasticsearch.csr -subj "/CN=elasticsearch" -sha256
8+
openssl x509 -req -in elasticsearch.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out elasticsearch.crt -days 365 -sha256
9+
10+
echo "Generating Logstash certificate"
11+
openssl req -newkey rsa:3072 -nodes -keyout logstash.key -out logstash.csr -subj "/CN=logstash" -sha256
12+
openssl x509 -req -in logstash.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out logstash.crt -days 365 -sha256
13+
14+
echo "Generating Filebeat certificate"
15+
openssl req -newkey rsa:3072 -nodes -keyout filebeat.key -out filebeat.csr -subj "/CN=filebeat" -sha256
16+
openssl x509 -req -in filebeat.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out filebeat.crt -days 365 -sha256
17+
18+
chmod 644 *.crt *.key
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
services:
2+
logstash:
3+
image: docker.elastic.co/logstash/logstash-observability-sre:${OBSERVABILITY_SRE_IMAGE_VERSION:-8.19.0-SNAPSHOT}
4+
container_name: fips_test_logstash
5+
ports:
6+
- "9600:9600"
7+
- "5044:5044"
8+
volumes:
9+
- ./logstash/config/${LOGSTASH_CONFIG:-logstash-fips.yml}:/usr/share/logstash/config/logstash.yml
10+
- ./logstash/pipeline/${LOGSTASH_PIPELINE:-logstash-to-elasticsearch.conf}:/usr/share/logstash/pipeline/logstash.conf
11+
- ./certs:/usr/share/logstash/config/certs
12+
environment:
13+
- ELASTIC_STACK_SECURITY_ENABLED=true
14+
- ELASTIC_SSL_CERTIFICATE_AUTHORITIES=/usr/share/logstash/config/certs/ca.crt
15+
- ELASTIC_SSL_CERTIFICATE=/usr/share/logstash/config/certs/logstash.crt
16+
- ELASTIC_SSL_KEY=/usr/share/logstash/config/certs/logstash.key
17+
networks:
18+
- elastic
19+
depends_on:
20+
- elasticsearch
21+
elasticsearch:
22+
image: docker.elastic.co/elasticsearch/elasticsearch-fips:${ELASTICSEARCH_IMAGE_VERSION:-8.19.0-SNAPSHOT}
23+
container_name: fips_test_elasticsearch
24+
ports:
25+
- "9200:9200"
26+
volumes:
27+
- ./elasticsearch/config/${ELASTICSEARCH_CONFIG:-elasticsearch-fips.yml}:/usr/share/elasticsearch/config/elasticsearch.yml
28+
- ./certs:/usr/share/elasticsearch/config/certs
29+
environment:
30+
- discovery.type=single-node
31+
- ES_JAVA_OPTS=-Xms1g -Xmx1g
32+
- ELASTIC_PASSWORD=changeme
33+
networks:
34+
- elastic
35+
# Filebeat is not yet used in tests, but this is included to show that including it in the compose network
36+
# will not adversely affect startup time etc for testing interactions between other components.
37+
filebeat:
38+
image: docker.elastic.co/elasticsearch/elasticsearch-fips:${FILEBEAT_IMAGE_VERSION:-8.19.0-SNAPSHOT}
39+
container_name: fips_test_filebeat
40+
entrypoint: ["filebeat", "-e", "--strict.perms=false", "-c", "/usr/share/filebeat/filebeat.yml"]
41+
volumes:
42+
- ./filebeat/config/${FILEBEAT_CONFIG:-filebeat-fips.yml}:/usr/share/filebeat/filebeat.yml
43+
- ./filebeat/data:/data
44+
- ./certs:/usr/share/filebeat/certs
45+
profiles:
46+
- filebeat
47+
networks:
48+
- elastic
49+
networks:
50+
elastic:
51+
driver: bridge
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Elasticsearch settings
2+
discovery.type: single-node
3+
http.port: 9200
4+
network.host: 0.0.0.0
5+
# Security settings
6+
xpack.security.enabled: true
7+
xpack.security.transport.ssl.enabled: true
8+
xpack.security.transport.ssl.verification_mode: certificate
9+
xpack.security.transport.ssl.key: /usr/share/elasticsearch/config/certs/elasticsearch.key
10+
xpack.security.transport.ssl.certificate: /usr/share/elasticsearch/config/certs/elasticsearch.crt
11+
xpack.security.transport.ssl.certificate_authorities: ["/usr/share/elasticsearch/config/certs/ca.crt"]
12+
xpack.security.http.ssl.enabled: true
13+
xpack.security.http.ssl.key: /usr/share/elasticsearch/config/certs/elasticsearch.key
14+
xpack.security.http.ssl.certificate: /usr/share/elasticsearch/config/certs/elasticsearch.crt
15+
xpack.security.http.ssl.certificate_authorities: ["/usr/share/elasticsearch/config/certs/ca.crt"]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
filebeat.inputs:
2+
- type: log
3+
enabled: true
4+
paths:
5+
- /test-logs/*.log
6+
7+
output.logstash:
8+
hosts: ["logstash:5044"]
9+
ssl.enabled: true
10+
ssl.certificate: "/usr/share/elasticsearch/config/certs/filebeat.crt"
11+
ssl.key: "/usr/share/elasticsearch/config/certs/filebeat.key"
12+
ssl.certificate_authorities: ["/usr/share/elasticsearch/config/certs/ca.crt"]
13+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"message":"FIPS compliance test message 1","timestamp":"2025-05-01T12:00:00Z","level":"info"}
2+
{"message":"FIPS compliance test message 2","timestamp":"2025-05-01T12:01:00Z","level":"debug"}
3+
{"message":"FIPS compliance test message 3","timestamp":"2025-05-01T12:02:00Z","level":"info"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
api.http.host: "0.0.0.0"
2+
xpack.monitoring.enabled: false
3+
4+
pipeline.ordered: false
5+
pipeline.workers: 2
6+
pipeline.buffer.type: heap
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
input {
2+
generator {
3+
# Generate this message indefinitely to give ES container time to come online
4+
count => -1
5+
lines => ["FIPS compliance test heartbeat"]
6+
}
7+
}
8+
9+
filter {
10+
mutate {
11+
add_field => {
12+
"fips_test" => "true"
13+
}
14+
}
15+
}
16+
17+
output {
18+
elasticsearch {
19+
hosts => ["https://elasticsearch:9200"]
20+
user => "elastic"
21+
password => "changeme"
22+
ssl_enabled => true
23+
ssl_verification_mode => "full"
24+
ssl_certificate_authorities => ["/usr/share/logstash/config/certs/ca.crt"]
25+
index => "logstash-fips-test-%{+YYYY.MM.dd}"
26+
ssl_supported_protocols => ["TLSv1.2"]
27+
}
28+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
require 'net/http'
2+
require 'uri'
3+
require 'json'
4+
require 'timeout'
5+
6+
describe "ObservabilitySRE FIPS container running on FIPS vm" do
7+
8+
before(:all) do
9+
# Start docker-compose and wait for ES
10+
system("cd #{__dir__}/../docker && docker-compose up -d") or fail "Failed to start Docker Compose environment"
11+
max_retries = 120
12+
retries = 0
13+
ready = false
14+
15+
while !ready && retries < max_retries
16+
begin
17+
# Wait for elasticsearch to be ready
18+
response = es_request("/_cluster/health")
19+
if response.code == "200"
20+
health = JSON.parse(response.body)
21+
if ["green", "yellow"].include?(health["status"])
22+
ready = true
23+
end
24+
end
25+
rescue => e
26+
puts "Waiting for Elasticsearch: #{e.message}"
27+
ensure
28+
unless ready
29+
retries += 1
30+
sleep 1
31+
puts "Retry #{retries}/#{max_retries}"
32+
end
33+
end
34+
end
35+
36+
raise "System not ready after #{max_retries} seconds" unless ready
37+
end
38+
39+
after(:all) do
40+
# stop docker network
41+
system("cd #{__dir__}/../docker && docker-compose down -v")
42+
end
43+
44+
it "data flows from Logstash to Elasticsearch using FIPS-approved SSL" do
45+
# Wait for index to appear, indicating data is flowing
46+
wait_until(timeout: 30, message: "Index logstash-fips-test not found") do
47+
response = es_request("/_cat/indices?v")
48+
response.code == "200" && response.body.include?("logstash-fips-test")
49+
end
50+
# Wait until specific data from logstash generator/mutate filters are observed
51+
query = { query: { match_all: {} } }.to_json
52+
result = nil
53+
wait_until(timeout: 30, message: "Index logstash-fips-test not found") do
54+
response = es_request("/logstash-fips-test-*/_search", query)
55+
result = JSON.parse(response.body)
56+
response.code == "200" && result["hits"]["total"]["value"] > 0
57+
end
58+
expect(result["hits"]["hits"].first["_source"]).to include("fips_test")
59+
end
60+
61+
def wait_until(timeout: 30, interval: 1, message: nil)
62+
Timeout.timeout(timeout) do
63+
loop do
64+
break if yield
65+
sleep interval
66+
end
67+
end
68+
rescue Timeout::Error
69+
raise message || "Condition not met within #{timeout} seconds"
70+
end
71+
72+
def es_request(path, body = nil)
73+
es_url = "https://localhost:9200"
74+
es_user = 'elastic'
75+
es_password = 'changeme'
76+
uri = URI.parse(es_url + path)
77+
http = Net::HTTP.new(uri.host, uri.port)
78+
http.use_ssl = true
79+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
80+
81+
request = body ? Net::HTTP::Post.new(uri.request_uri) : Net::HTTP::Get.new(uri.request_uri)
82+
request.basic_auth(es_user, es_password)
83+
request["Content-Type"] = "application/json"
84+
request.body = body if body
85+
86+
http.request(request)
87+
end
88+
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.logstash.xpack.test;
2+
3+
import org.junit.Test;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
7+
public class RSpecObservabilitySREAcceptanceTests extends RSpecTests {
8+
@Override
9+
protected List<String> rspecArgs() {
10+
return Arrays.asList("-fd", "distributions/internal/observabilitySRE/qa/acceptance/spec");
11+
}
12+
13+
@Test
14+
@Override
15+
public void rspecTests() throws Exception {
16+
super.rspecTests();
17+
}
18+
}

0 commit comments

Comments
 (0)