This HOWTO assumes Red Hat Enterprise Linux 9.x or 10.x with Podman. Even though this is a lab, the assumptions are that the skeleton could be scaled to something in production without major reconstructions, hence the reason for the elaborate TLS incantations that could easily be replaced by requesting certificates to be signed by another Root CA.
Download containers
podman pull docker.elastic.co/elastic-agent/elastic-agent:9.2.0
podman pull docker.elastic.co/kibana/kibana:9.2.0
podman pull docker.elastic.co/elasticsearch/elasticsearch:9.2.0
Create a separate network
podman network create elastic
Tune kernel for Elasticsearch
cat << EOF > /etc/sysctl.d/02-elastic.conf
vm.max_map_count = 262144
vm.swappiness = 1
EOF
sysctl --system --load
Create directory structure for container certificates and config
mkdir -p /var/elastic
chcon -R system_u:object_r:container_file_t:s0 /var/elastic
cd /var/elastic
-
Create a self-signed Root CA
openssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -sha256 -days $((10*365)) -extensions v3_ca -out ca.crt \ -subj "/C=NL/ST=Fork Fountain/L=Numerous Dahlia/O=Zippy Mice/OU=SOC/CN=ROOT CA 2025" -
Create a key, CSR and certificate for Elasticsearch
openssl genrsa -out elastic.key 2048 openssl req -new -key elastic.key -out elastic.csr \ -subj "/C=NL/ST=Fork Fountain/L=Numerous Dahlia/O=Zippy Mice/OU=SOC/CN=elastic" \ -addext "subjectAltName=DNS:elastic.zippymice.internal" openssl x509 -req -in elastic.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -out elastic.crt -days $((5*365)) -sha256 -copy_extensions=copyall -
Create a key, CSR and certificate for Fleet
openssl genrsa -out fleet.key 2048 openssl req -new -key fleet.key -out fleet.csr \ -subj "/C=NL/ST=Fork Fountain/L=Numerous Dahlia/O=Zippy Mice/OU=SOC/CN=fleet" \ -addext "subjectAltName=DNS:fleet.zippymice.internal" openssl x509 -req -in fleet.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -out fleet.crt -days $((5*365)) -sha256 -copy_extensions=copyall -
Create a key, CSR and certificate for Kibana
openssl genrsa -out kibana.key 2048 openssl req -new -key kibana.key -out kibana.csr \ -subj "/C=NL/ST=Fork Fountain/L=Numerous Dahlia/O=Zippy Mice/OU=SOC/CN=kibana" \ -addext "subjectAltName=DNS:kibana.zippymice.internal" openssl x509 -req -in kibana.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -out kibana.crt -days $((5*365)) -sha256 -copy_extensions=copyall -
Move certificates into place
mkdir /var/elastic/certs-elastic cp ca.crt /var/elastic/certs-elastic mv elastic.key /var/elastic/certs-elastic cat elastic.crt ca.crt > /var/elastic/certs-elastic/elastic.crt rm -f elastic.crt chmod 755 /var/elastic/certs-elastic chmod 644 /var/elastic/certs-elastic/* mkdir /var/elastic/data-elastic chmod 750 /var/elastic/data-elastic chown 1000:0 /var/elastic/data-elastic mkdir /var/elastic/certs-kibana cp ca.crt /var/elastic/certs-kibana mv kibana.key /var/elastic/certs-kibana cat kibana.crt ca.crt > /var/elastic/certs-kibana/kibana.crt rm -f kibana.crt chmod 755 /var/elastic/certs-kibana chmod 644 /var/elastic/certs-kibana/* mkdir /var/elastic/certs-fleet cp ca.crt /var/elastic/certs-fleet mv fleet.key /var/elastic/certs-fleet cat fleet.crt ca.crt > /var/elastic/certs-fleet/fleet.crt rm -f fleet.crt chmod 755 /var/elastic/certs-fleet chmod 644 /var/elastic/certs-fleet/*
Create the Elasticsearch config file.
cat << EOF > /var/elastic/elastic.yml
xpack.security.enabled: true
network.host: 0.0.0.0
xpack.security:
http.ssl:
enabled: true
key: /usr/share/elasticsearch/config/certificates/elastic.key
certificate_authorities: /usr/share/elasticsearch/config/certificates/ca.crt
certificate: /usr/share/elasticsearch/config/certificates/elastic.crt
verification_mode: certificate
transport.ssl:
enabled: true
key: /usr/share/elasticsearch/config/certificates/elastic.key
certificate: /usr/share/elasticsearch/config/certificates/elastic.crt
certificate_authorities: /usr/share/elasticsearch/config/certificates/ca.crt
verification_mode: certificate
node.name: elastic
cluster:
initial_master_nodes: elastic
name: VJH
routing.allocation.disk.watermark:
low: 90%
high: 95%
EOF
chmod 644 /var/elastic/elastic.yml
chown 1000:0 /var/elastic/elastic.yml
Generate a service token so Kibana can use it so access Elasticsearch. (Copy it for use in the later Kibana step)
podman run -d --name tmp1 docker.elastic.co/elasticsearch/elasticsearch:9.2.0
podman exec tmp1 bin/elasticsearch-service-tokens create elastic/kibana kibana-service-token
podman exec tmp1 cat config/service_tokens >service_tokens
podman stop tmp1
podman rm tmp1
chmod 644 service_tokens
Start the Elasticsearch container
podman run --detach --name elastic --hostname elastic \
-p 9200:9200 \
-p 9300:9300 \
--net=elastic \
--memory=16GB \
--cpus=2 \
--detach-keys=p,q \
-v "/var/elastic/data-elastic:/usr/share/elasticsearch/data" \
-v "/var/elastic/elastic.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro" \
-v "/var/elastic/service_tokens:/usr/share/elasticsearch/config/service_tokens" \
-v "/var/elastic/certs-elastic:/usr/share/elasticsearch/config/certificates:ro" \
docker.elastic.co/elasticsearch/elasticsearch:9.2.0
Open the Elasticsearch port in the local firewall
firewall-cmd --add-port=9200/tcp --permanent
firewall-cmd --reload
Reset the password for the elastic admin user if you don't want to hunt for it in the logs
podman exec -it elastic bin/elasticsearch-reset-password -u elastic
Create the Kibana config and remember to paste the service token you generated earlier into it.
cat << EOF > /var/elastic/kibana.yml
elasticsearch:
hosts: https://elastic:9200
ssl.certificateAuthorities: /usr/share/kibana/config/certificates/ca.crt
serviceAccountToken: PASTE_SERVICE_TOKEN_FROM_PREVIOUS_STEP_HERE
server:
publicBaseUrl: https://kibana:5601
host: 0.0.0.0
ssl.enabled: true
ssl.certificate: /usr/share/kibana/config/certificates/kibana.crt
ssl.key: /usr/share/kibana/config/certificates/kibana.key
ssl.certificateAuthorities: /usr/share/kibana/config/certificates/ca.crt
telemetry.optIn: false
newsfeed.enabled: false
xpack:
fleet.isAirGapped: true
encryptedSavedObjects.encryptionKey: replace-with-a-minimum-32-byte-long-encryption-key
security.encryptionKey: replace-with-a-minimum-32-byte-long-encryption-key
reporting.encryptionKey: replace-with-a-minimum-32-byte-long-encryption-key
EOF
chmod 644 kibana.yml
Open the Kibana port in the host firewall
firewall-cmd --add-port=5601/tcp --permanent
firewall-cmd --reload
firewall-cmd --list-all
Start the Kibana container
podman run --detach --name kibana --hostname kibana \
-p 5601:5601 \
--net=elastic \
--memory=4GB \
--cpus=1 \
--detach-keys=p,q \
-v "/var/elastic/kibana.yml:/usr/share/kibana/config/kibana.yml:ro" \
-v "/var/elastic/certs-kibana:/usr/share/kibana/config/certificates:ro" \
docker.elastic.co/kibana/kibana:9.2.0
In Kibana -> Fleet -> Add Fleet Server -> Advanced (don't use Quick Start). Copy the Fleet Server service token into the command below.
podman run --detach --name fleet --hostname fleet \
-p 8220:8220 \
-v "/var/elastic/certs-fleet:/etc/fleet:ro" \
--env FLEET_URL=https://fleet:8220 \
--env FLEET_SERVER_ENABLE=true \
--env FLEET_SERVER_ELASTICSEARCH_HOST=https://elastic:9200 \
--env FLEET_SERVER_SERVICE_TOKEN=COPY_FLEET_SERVER_SERVICE_TOKEN_HERE \
--env FLEET_SERVER_POLICY_ID=fleet-server-policy \
--env FLEET_SERVER_PORT=8220 \
--env FLEET_CA=/etc/fleet/ca.crt \
--env FLEET_SERVER_ELASTICSEARCH_CA=/etc/fleet/ca.crt \
--env FLEET_SERVER_CERT=/etc/fleet/fleet.crt \
--env FLEET_SERVER_CERT_KEY=/etc/fleet/fleet.key \
docker.elastic.co/elastic-agent/elastic-agent:9.2.0
Open the Fleet port in the local host firewall:
firewall-cmd --add-port=8220/tcp --permanent
firewall-cmd --reload