Skip to content

Instantly share code, notes, and snippets.

@linuxmalaysia
Created April 1, 2025 23:43
Show Gist options
  • Save linuxmalaysia/7782c879be1e22469d39bb1557505623 to your computer and use it in GitHub Desktop.
Save linuxmalaysia/7782c879be1e22469d39bb1557505623 to your computer and use it in GitHub Desktop.
Script to set up Kibana using Podman with the hardened Wolfi image. This script should be run after setup_elasticsearch.sh.
#!/bin/bash
# Script to set up Kibana using Podman with the hardened Wolfi image,
# based on the official Docker documentation.
# Note: Using Wolfi images might have specific kernel or dependency requirements.
# https://www.elastic.co/guide/en/kibana/current/docker.html
# GNU GENERAL PUBLIC LICENSE Version 3
# Harisfazillah Jamel and Google Gemini
# 2 Apr 2025
# Script to set up Kibana using Podman with its own custom kibana.yml.
# This script should be run after setup_elasticsearch.sh.
set -e # Exit immediately if a command exits with a non-zero status.
# --- Determine Script's Directory ---
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
# Get the directory where the script is located.
# dirname: Extracts the directory name from a path.
# realpath: Resolves symbolic links to their actual path.
# $0: Represents the path of the script being executed.
# --- Variables ---
ELK_BASE_DIR="${SCRIPT_DIR}" # Base directory is where the script is located.
ELK_DIR="${ELK_BASE_DIR}/elk-wolfi" # Directory for ELK-related files.
CERT_DIR="${ELK_DIR}/certs" # Directory for SSL certificates.
KIBANA_IMAGE_NAME="docker.elastic.co/kibana/kibana-wolfi" # Name of the Kibana Docker image.
KIBANA_CONTAINER_NAME="kib01" # Name for the Kibana container.
KIBANA_PORT="5601" # Port on which Kibana will be accessible.
NETWORK_NAME="elk-wolfi_elastic" # Name of the Podman network. Important for Elasticsearch/Kibana communication.
TEMP_CREDENTIALS_FILE="${ELK_DIR}/temp_credentials.txt" # File to store temporary credentials (like Elasticsearch password).
# --- Helper Functions ---
info() {
echo "--- $1 ---" # Function to print informational messages with a separator.
}
command_exists () {
command -v "$1" >/dev/null 2>&1
# Checks if a command exists in the system's PATH.
# command -v: Checks if a command is available.
# >/dev/null 2>&1: Silences both standard output and standard error.
}
# --- Step 1: Check Prerequisites ---
info "Step 1: Check Prerequisites"
if ! command_exists podman; then
echo "Error: Podman is not installed. Please run the setup_elasticsearch.sh script first or install Podman."
exit 1 # Exit with an error code 1 to indicate failure.
fi
if ! command_exists podman-compose; then
echo "Error: podman-compose is not installed. Please run the setup_elasticsearch.sh script first or install podman-compose."
exit 1
fi
# --- Step 2: Check for Certificate File ---
info "Step 2: Check for Elasticsearch Certificate"
CERT_FILE="${CERT_DIR}/http_ca.crt" # Define the path to the Elasticsearch certificate.
if [ ! -f "${CERT_FILE}" ]; then
echo "Error: Elasticsearch certificate file not found at '${CERT_FILE}'. Please ensure the setup_elasticsearch.sh script was run successfully."
exit 1 # Exit if the certificate file is not found.
fi
# --- Check Elasticsearch Network ---
info " Step 2.1: Check Elasticsearch Network"
if ! podman network exists "${NETWORK_NAME}"; then # Check if the network exists.
echo "Error: The Podman network '${NETWORK_NAME}' does not exist."
echo "Please ensure that the setup_elasticsearch.sh script was run successfully and created this network."
exit 1
fi
info "Podman network '${NETWORK_NAME}' exists."
# --- Step 3: Check Elasticsearch Status and Get Version ---
info "Step 3: Check Elasticsearch Status and Get Version"
# Attempt to retrieve the Elasticsearch password from the temporary file and clean it
if [ -f "${TEMP_CREDENTIALS_FILE}" ]; then
ELASTIC_PASSWORD=$(grep "Elastic password set to:" "${TEMP_CREDENTIALS_FILE}" | sed 's/.*Elastic password set to: //' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
# Get the Elasticsearch password from the temporary credentials file.
# grep: Finds the line containing "Elastic password set to:".
# sed: Used to extract the password from the line.
# s/.*Elastic password set to: //: Removes everything before the password.
# s/^[[:space:]]*//: Removes leading spaces.
# s/[[:space:]]*$//: Removes trailing spaces.
else
echo "Error: Temporary credentials file '${TEMP_CREDENTIALS_FILE}' not found. Please ensure the setup_elasticsearch.sh script was run successfully."
exit 1
fi
if [ -z "${ELASTIC_PASSWORD}" ]; then
echo "Error: Elasticsearch password not found in '${TEMP_CREDENTIALS_FILE}'. Please check the file."
exit 1
fi
ES_STATUS=$(curl -s --cacert "${CERT_FILE}" -u "elastic:${ELASTIC_PASSWORD}" "https://localhost:9200")
# Check the status of Elasticsearch.
# curl: Makes an HTTP request to Elasticsearch.
# -s: Silent mode.
# --cacert: Specifies the CA certificate for SSL verification.
# -u: Specifies the username and password for authentication.
# https://localhost:9200: The default Elasticsearch API endpoint.
if [[ "$ES_STATUS" == *"You Know, for Search"* ]]; then
info "Elasticsearch is running."
ELASTICSEARCH_VERSION=$(echo "$ES_STATUS" | jq -r '.version.number')
# Extract the Elasticsearch version from the JSON output.
# jq: A command-line JSON processor.
# -r: Raw output (prints the value without quotes).
# .version.number: JSON path to the version number.
info "Elasticsearch version found: ${ELASTICSEARCH_VERSION}"
else
echo "Error: Elasticsearch is not running or the status check failed."
echo "Status output: ${ES_STATUS}"
exit 1
fi
# --- Step 4: Pull Kibana Docker Image ---
info "Step 4: Pull Kibana Docker Image"
KIBANA_IMAGE="${KIBANA_IMAGE_NAME}:${ELASTICSEARCH_VERSION}" # Tag the Kibana image with the Elasticsearch version.
podman pull "${KIBANA_IMAGE}" # Pull the Kibana image from the container registry.
# --- Step 5: Get Default Kibana Configuration ---
info "Step 5: Get Default Kibana Configuration"
TEMP_KIBANA_CONTAINER="temp_kib01" # Name for a temporary Kibana container.
# --- Step 5.1: Create ELK Directory on Host ---
info "Step 5.1: Create ELK Directory on Host"
mkdir -p "${ELK_DIR}" # Create the ELK directory on the host if it doesn't exist.
echo "Starting temporary Kibana container '${TEMP_KIBANA_CONTAINER}' to extract default config..."
podman run --name "${TEMP_KIBANA_CONTAINER}" --network "${NETWORK_NAME}" -d "${KIBANA_IMAGE}" sleep infinity
# Start a temporary Kibana container in detached mode.
# podman run: Runs a container.
# --name: Assigns a name to the container.
# --network: Connects the container to the specified network.
# -d: Detached mode (runs in the background).
# sleep infinity: Keeps the container running indefinitely. We use this because we only need the container to be running to copy the file, not to execute a service.
if [ $? -eq 0 ]; then # Check if the podman run command was successful.
echo "Copying default kibana.yml from container..."
podman cp "${TEMP_KIBANA_CONTAINER}:/usr/share/kibana/config/kibana.yml" "${ELK_DIR}/kibana.yml"
# Copy the default kibana.yml file from the container to the host.
# podman cp: Copies files between a container and the host.
echo "Default kibana.yml copied to ${ELK_DIR}/kibana.yml. Please review and customize it."
echo "Stopping and removing temporary container '${TEMP_KIBANA_CONTAINER}'..."
podman stop "${TEMP_KIBANA_CONTAINER}" # Stop the temporary container.
podman rm "${TEMP_KIBANA_CONTAINER}" # Remove the temporary container.
else
echo "Error starting temporary Kibana container. Skipping default config copy."
exit 1
fi
# --- Step 6: Start a Kibana container using podman-compose with volume and custom config ---
info "Step 6: Start a Kibana container using podman-compose with volume and custom config"
cat > "${ELK_DIR}/podman-compose-kibana.yml" <<EOL
version: '3.8' # Use version 3.8 of the Compose file format. Specifies the version of the Compose file syntax.
services:
kibana:
image: ${KIBANA_IMAGE} # The Docker image to use for the Kibana service.
container_name: ${KIBANA_CONTAINER_NAME} # The name of the container.
networks:
- ${NETWORK_NAME} # Connect the Kibana container to the specified network, allowing it to communicate with Elasticsearch.
ports:
- "${KIBANA_PORT}:${KIBANA_PORT}" # Map the Kibana port (5601) on the host to the same port in the container.
volumes:
- kibana_data:/data/kibana_data # Mount a volume for Kibana data. This persists data across container restarts.
- ./kibana.yml:/usr/share/kibana/config/kibana.yml # Mount custom kibana.yml to standard config dir
volumes:
kibana_data: # Declare a named volume for Kibana data. This allows Podman to manage the volume.
networks:
${NETWORK_NAME}:
external: true # Declare the network as external, meaning it was created outside of this Compose file.
EOL
# podman-compose.yml: This file defines how to set up and run the Kibana container.
# version: The version of the Compose file format.
# services: Defines the services that make up the application (in this case, just Kibana).
# kibana: Defines the Kibana service.
# image: The Docker image to use for Kibana.
# container_name: The name of the container.
# networks: The networks to connect the container to.
# ports: Port mappings between the host and the container.
# environment: Environment variables for the Kibana container.
# volumes: Volume mappings for persistent data and configuration.
# networks: Defines the networks used by the services.
# external: Indicates that the network was created outside of this Compose file.
cd "${ELK_DIR}" # Change to the directory containing the podman-compose file.
podman-compose -f podman-compose-kibana.yml up -d
# Start Kibana using podman-compose.
# -f: Specifies the Compose file to use.
# up: Starts the services defined in the Compose file.
# -d: Runs the services in detached mode (in the background).
# --- Step 7: Wait for Kibana Container to be Running ---
info "Step 7: Wait for Kibana Container to be Running"
MAX_WAIT_SECONDS=60 # Maximum number of seconds to wait for Kibana to start.
echo "Please wait for Kibana to start..."
for i in $(seq "$MAX_WAIT_SECONDS" -1 1); do
echo "Waiting for Kibana to start... $i seconds remaining..."
podman ps -a --filter name="${KIBANA_CONTAINER_NAME}"
# Check the status of the Kibana container.
# podman ps -a: Lists all containers (running and stopped).
# --filter: Filters the output by container name.
sleep 1 # Wait for 1 second before checking again.
done
echo "Kibana start process waiting complete. You can check the status above."
# --- Step 8: Get Elasticsearch Container IP Address ---
info "Step 8: Get Elasticsearch Container IP Address"
ES01_IP=$(podman inspect es01 | grep "elk-wolfi_elastic" -A 10 | grep "IPAddress" | sed -e 's/.*: "//' -e 's/,"//' -e 's/ //g')
echo "Elasticsearch (es01) IP Address: ${ES01_IP}"
# Get the IP address of the Elasticsearch container. This is needed for Kibana configuration.
# podman inspect: Retrieves detailed information about a container.
# es01: The name of the Elasticsearch container.
# grep "elk-wolfi_elastic": Filters the output to show information about the specified network.
# -A 10: Displays the 10 lines after the line containing the network name.
# grep "IPAddress": Filters the output to show the line containing the IP address.
# sed: Extracts the IP address from the line.
# -e 's/.*: "//': Removes everything before the IP address and the colon.
# -e 's/,"//': Removes the comma and double quote after the IP address.
# -e 's/ //g': Removes any remaining spaces.
# --- Step 9: Retrieve and Clean Kibana Enrollment Token ---
info "Retrieving Kibana enrollment token..."
KIBANA_ENROLLMENT_TOKEN=$(podman exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana 2>>"${TEMP_CREDENTIALS_FILE}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
# Retrieve the Kibana enrollment token from the Elasticsearch container. This token is used to enroll Kibana into the Elasticsearch cluster.
# podman exec: Executes a command inside a running container.
# -it: Interactive (allows you to see output).
# es01: The name of the Elasticsearch container (assumed to be running).
# /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana: The command to generate the enrollment token for Kibana.
# 2>>"${TEMP_CREDENTIALS_FILE}": Redirect standard error (2) to the temporary credentials file, appending to it.
# sed: Remove leading/trailing spaces.
# -e 's/^[[:space:]]*//': Removes leading spaces.
# -e 's/[[:space:]]*$//': Removes trailing spaces.
if [ -n "${KIBANA_ENROLLMENT_TOKEN}" ]; then #check if the token is not empty
echo "Kibana enrollment token: ${KIBANA_ENROLLMENT_TOKEN}"
if grep -q "^Kibana enrollment token:" "${TEMP_CREDENTIALS_FILE}"; then
# Replace the existing line
sed -i "s/^Kibana enrollment token:.*$/Kibana enrollment token: ${KIBANA_ENROLLMENT_TOKEN}/" "${TEMP_CREDENTIALS_FILE}"
# sed -i : in-place substitution
# s/old/new/ : substitute old with new
# ^Kibana enrollment token:.*$ : find the line starting with Kibana enrollment and replace the whole line.
else
# Append a new line
echo "Kibana enrollment token: ${KIBANA_ENROLLMENT_TOKEN}" >> "${TEMP_CREDENTIALS_FILE}"
fi
else
echo "Error retrieving Kibana enrollment token. Check ${TEMP_CREDENTIALS_FILE}"
fi
echo ""
info "Kibana setup script complete!"
echo ""
info "You can access the Kibana from your Internet Browser with this URL http://localhost:5601"
echo ""
info "Retrieve Kibana Verification Code:"
podman exec -it kib01 /usr/share/kibana/bin/kibana-verification-code
# Display the command to retrieve the Kibana verification code. This code is needed for the initial setup of Kibana.
# podman exec: Execute command in container.
# -it: Interactive shell.
# kib01: The name of the Kibana container.
# /usr/share/kibana/bin/kibana-verification-code: The command to display the verification code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment