wazuh-docker icon indicating copy to clipboard operation
wazuh-docker copied to clipboard

Rootless podman with SELinux

Open leonidas-o opened this issue 6 months ago • 2 comments

OS Rocky Linux 9.5 (Blue Onyx) podman-compose version 1.3.0 podman version 5.2.2

I managed to deploy Wazuh on a rootless podman setup with just little modifications. I would like to share it, also in hope that these modifications will be incorporated, as they shouldn't introduce a breaking change. With further testing, this could lead to official rootless podman support.


System setup
# as root
vi /etc/sysctl.conf
# add the follwing line
# vm.max_map_count=262144
reboot
# check if updated
sysctl -a |grep vm.max_map_count
# install new packages
dnf upgrade
dnf install podman python3-pip netavark aardvark-dns
vi /usr/share/containers/containers.conf # set network_backend = "netavark"

# The systemd user instance is killed after the last session for the user is closed. 
# The systemd user instance can be started at boot and kept running even after the 
# user logs out by enabling lingering using
loginctl enable-linger myuser
# as regular user
pip3 install --user podman-compose
pip3 install --upgrade podman-compose pip
sudop mkdir -p /opt/wazuh
sudo chown -R myuser:mygroup /opt/wazuh
cd /opt/wazuh
# as root
restorecon -vv -F -r /

Unprivileged Ports

sudo su -
# if needed to bind to ports < 1024, will be reset after reboot!
echo 443 > /proc/sys/net/ipv4/ip_unprivileged_port_start
# if needed permanently
echo "net.ipv4.ip_unprivileged_port_start=443" >> /etc/sysctl.conf

ulimit -aH
ulimit -aS
vi /etc/security/limits.conf
# Exit and re-login from the terminal for the change to take effect.
#<domain>      <type>  <item>         <value>
...
myuser        hard    memlock         -1
myuser        hard    nofile          655360

Firewall on VM

sudo su -
firewall-cmd --list-all
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --reload
firewall-cmd --list-all

Initial Deployment

podman-compose --in-pod=true up -d without file changes:

cp: cannot create regular file '/certificates/admin-key.pem': Permission denied
cp: cannot create regular file '/certificates/admin.pem': Permission denied
cp: cannot create regular file '/certificates/root-ca.key': Permission denied
cp: cannot create regular file '/certificates/root-ca.pem': Permission denied
...

Modifications

add :z to generate-indexer-certs.yml bind mounts:

...
    volumes:
      - ./config/wazuh_indexer_ssl_certs/:/certificates/:z
      - ./config/certs.yml:/config/certs.yml:z

also add :z to all bind mounts inside docker-compose.yml (for nginx bind mounts :ro,z)

Final Deployment

podman-compose --in-pod=true up -d

Everything should come up as usual.

leonidas-o avatar May 15 '25 19:05 leonidas-o

Hello, thank you for your contribution. We will analyze what was proposed and try to incorporate the changes for our version 5.0.0.

c-bordon avatar May 20 '25 18:05 c-bordon

@c-bordon no thank you! I submitted a PR: https://github.com/wazuh/wazuh-docker/pull/1834 Wazuh manager in rootless podman seems to work so far.

FYI: I'm looking into the agent right now. The plan is to create a daemonSet for Openshifts restricted env. The idea is to have a rootless image based on rockylinux:9-ubi-micro. Will see how that works out, so far SETGID cap is causing some issues.

leonidas-o avatar May 20 '25 19:05 leonidas-o

Hello, I have quadlet setup, it runs, but need some tunning with the host where is installed, because not finish run because /proc and /sys, right now I don't have time to continue until next week, but if you found the solution here is the script [rootless] (My system use selinux enforce):

@leonidas-o @c-bordon

#!/usr/bin/env bash
set -euo pipefail

## ========== ##
##    Init    ##
## ========== ##

# Start podman socker and auto-update timer
if [ $(systemctl --user is-enabled podman.socket) == "disabled" ]; then
	systemctl --user enable --now podman.socket podman-auto-update.timer
fi

## ================== ##
##    Environments    ##
## ================== ##
APP_NAME="wazuh"

## Rutas
ROOTLESS_PATH="${HOME}/.config/containers"
QUADLET_PATH="${ROOTLESS_PATH}"/systemd/${APP_NAME}
VOLUMES_PATH="${HOME}/.volumes"

# container setup for rootless
if [ ! -d "${ROOTLESS_PATH}" ]; then
	rsync -avx --exclude="*storage.conf*" --ignore-existing /usr/share/containers "${ROOTLESS_PATH}"

	#### CHANGE IF THE USER IS NOT INSTALLED IN A BTRFS PARTTION TO overlay"
	printf '[storage]\ndriver = "btrfs"\n' >"${ROOTLESS_PATH}"/storage.conf
fi

# Create the systemd-quadlet spaces
[ ! -d "${QUADLET_PATH}" ] && mkdir -vp "${QUADLET_PATH}"

## ============= ##
##    Volumes    ##
## ============= ##
VOLUMES=(
	"wazuh_api_configuration"
	"wazuh_etc"
	"wazuh_logs"
	"wazuh_queue"
	"wazuh_var_multigroups"
	"wazuh_active_response"
	"wazuh_wodles"
	"filebeat_etc"
	"filebeat_var"
	"wazuh-indexer-data"
	"wazuh-dashboard-config"
	"wazuh-dashboard-custom"
)
for volume in "${VOLUMES[@]}"; do
	# Creación del directorio
	mkdir -p "${VOLUMES_PATH}/${volume}"

	# Limpieza del nombre para el archivo .volume
	volume_file_name=$(echo "${volume}" | tr '/' '-')

	cat >${QUADLET_PATH}/${volume_file_name}.volume <<EOF
[Unit]
Description=Volume for ${APP_NAME} ${volume^}

[Quadlet]
# Disable dependency "podman-user-wait-network-online.service"
DefaultDependencies=false

[Volume]
Type=none
Device=${VOLUMES_PATH}/${volume}
Options=rbind,rw
EOF
done

## ================== ##
##    Devs configs    ##
## ================== ##

# 1) Light Clone
tmpdir="$(mktemp -d)"
command -v git >/dev/null 2>&1 || zypper in -y git-core
git clone --depth 1 --filter=blob:none --sparse --single-branch --branch v4.13.1 \
	https://github.com/wazuh/wazuh-docker.git "$tmpdir"
cd "$tmpdir"

# 2) Ask only one folder
git sparse-checkout set single-node/config

# 3) Copy the contain to the correct volume
mkdir -p "${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs"
cp -a single-node/config/* "${VOLUMES_PATH}/config/"

# 4) Limpiar
cd
rm -rf "$tmpdir"

## ============= ##
##    Network    ##
## ============= ##

cat >${QUADLET_PATH}/${APP_NAME,,}.network <<EOF
[Unit]
Description=${APP_NAME} Network
After=network.target

[Install]
WantedBy=default.target
EOF

## ========= ##
##    Pod    ##
## ========= ##
cat >${QUADLET_PATH}/${APP_NAME,,}.pod <<EOF
[Unit]
Description=${APP_NAME} POD

[Pod]
PodName=${APP_NAME,,}
Network=${APP_NAME,,}.network

# Wazuh Manager - Ports
PublishPort=1514:1514
PublishPort=1515:1515
PublishPort=514:514/udp
PublishPort=55000:55000

# Wazuh Indexer - Ports
PublishPort=9200:9200

# Wazuh Dasshboard - Ports
PublishPort=443:5601

[Service]
# Restart service when sleep finishes
Restart=always
# Extend Timeout to allow time to pull the image
TimeoutStartSec=900

[Install]
WantedBy=default.target
EOF

## =============== ##
##    Container    ##
## =============== ##

##
## Wazuh Manager
##
cat >${QUADLET_PATH}/${APP_NAME,,}-manager.container <<EOF
[Unit]
Description="${APP_NAME} Manager"

[Service]
# Restart service when sleep finishes
Restart=always
# Extend Timeout to allow time to pull the image
TimeoutStartSec=900

[Container]
Pod=${APP_NAME,,}.pod

# Imagen y actualización
Image=docker.io/wazuh/wazuh-manager:4.13.1
AutoUpdate=registry
Pull=newer

# Ulimit
Ulimit=memlock=-1:-1
Ulimit=nofile=65536:65536

# Variables de Entornos
Environment="INDEXER_URL=https://localhost:9200"
Environment="INDEXER_USERNAME=admin"
Environment="INDEXER_PASSWORD=SecretPassword"
Environment="FILEBEAT_SSL_VERIFICATION_MODE=full"
Environment="SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem"
Environment="SSL_CERTIFICATE=/etc/ssl/filebeat.pem"
Environment="SSL_KEY=/etc/ssl/filebeat.key"
Environment="API_USERNAME=wazuh-wui"
Environment="API_PASSWORD=MyS3cr37P450r.*--"

# Volúmenes
Volume=wazuh_api_configuration.volume:/var/ossec/api/configuration:Z
Volume=wazuh_etc.volume:/var/ossec/etc:Z
Volume=wazuh_logs.volume:/var/ossec/logs:Z
Volume=wazuh_queue.volume:/var/ossec/queue:Z
Volume=wazuh_var_multigroups.volume:/var/ossec/var/multigroups:Z
Volume=wazuh_active_response.volume:/var/ossec/active-response/bin:Z
Volume=wazuh_wodles.volume:/var/ossec/wodles:Z
Volume=filebeat_etc.volume:/etc/filebeat:Z
Volume=filebeat_var.volume:/var/lib/filebeat:Z
Volume=${VOLUMES_PATH}/./config/wazuh_indexer_ssl_certs/root-ca-manager.pem:/etc/ssl/root-ca.pem:Z
Volume=${VOLUMES_PATH}/./config/wazuh_indexer_ssl_certs/wazuh.manager.pem:/etc/ssl/filebeat.pem:Z
Volume=${VOLUMES_PATH}/./config/wazuh_indexer_ssl_certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key:Z
Volume=${VOLUMES_PATH}/./config/wazuh_cluster/wazuh_manager.conf:/wazuh-config-mount/etc/ossec.conf:Z
EOF

##
## Wazuh Indexer
##
cat >${QUADLET_PATH}/${APP_NAME,,}-indexer.container <<EOF
[Unit]
Description="${APP_NAME} indexer"

[Service]
# Restart service when sleep finishes
Restart=always
# Extend Timeout to allow time to pull the image
TimeoutStartSec=900

[Container]
Pod=${APP_NAME,,}.pod

# Imagen y actualización
Image=docker.io/wazuh/wazuh-indexer:4.13.1
AutoUpdate=registry
Pull=newer

# Ulimit
Ulimit=memlock=-1:-1
Ulimit=nofile=65536:65536

# Variables de entornor del contenedor
Environment="OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g"

# Volúmenes
Volume=wazuh-indexer-data.volume:/var/lib/wazuh-indexer:Z
Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-indexer/config/certs/root-ca.pem:Z
Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.key:Z
Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem:Z
Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/admin.pem:/usr/share/wazuh-indexer/config/certs/admin.pem:Z
Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/admin-key.pem:/usr/share/wazuh-indexer/config/certs/admin-key.pem:Z
Volume=${VOLUMES_PATH}/config/wazuh_indexer/wazuh.indexer.yml:/usr/share/wazuh-indexer/config/opensearch.yml:Z
Volume=${VOLUMES_PATH}/config/wazuh_indexer/internal_users.yml:/usr/share/wazuh-indexer/config/opensearch-security/internal_users.yml:Z
EOF

##
## Wazuh Dashboard
##

cat >${QUADLET_PATH}/${APP_NAME,,}-dashboard.container <<EOF
[Unit]
Description="${APP_NAME} dashboard"

[Service]
# Restart service when sleep finishes
Restart=always
# Extend Timeout to allow time to pull the image
TimeoutStartSec=900

[Container]
Pod=${APP_NAME,,}.pod

# Imagen y actualización
Image=docker.io/wazuh/wazuh-dashboard:4.13.1
AutoUpdate=registry
Pull=newer

# Variables de entornor del contenedor
Environment="INDEXER_USERNAME=admin"
Environment="INDEXER_PASSWORD=SecretPassword"
Environment="WAZUH_API_URL=https://localhost"
Environment="DASHBOARD_USERNAME=kibanaserver"
Environment="DASHBOARD_PASSWORD=kibanaserver"
Environment="API_USERNAME=wazuh-wui"
Environment="API_PASSWORD=MyS3cr37P450r.*-"

# Volúmenes
Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/wazuh.dashboard.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard.pem:Z
Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/wazuh.dashboard-key.pem:/usr/share/wazuh-dashboard/certs/wazuh-dashboard-key.pem:Z
Volume=${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem:Z
Volume=${VOLUMES_PATH}/config/wazuh_dashboard/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml:Z
Volume=${VOLUMES_PATH}/config/wazuh_dashboard/wazuh.yml:/usr/share/wazuh-dashboard/data/wazuh/config/wazuh.yml:Z
Volume=wazuh-dashboard-config.volume:/usr/share/wazuh-dashboard/data/wazuh/config:Z
Volume=wazuh-dashboard-custom.volume:/usr/share/wazuh-dashboard/plugins/wazuh/public/assets/custom:Z
EOF

## =========== ##
##    Tools    ##
## =========== ##

podman run --rm -it --name wazuh-certs-generator \
	-v ${VOLUMES_PATH}/config/wazuh_indexer_ssl_certs/:/certificates/:Z \
	-v ${VOLUMES_PATH}/config/certs.yml:/config/certs.yml:Z \
	docker.io/wazuh/wazuh-certs-generator:0.0.2

## ================== ##
##    Verify Units    ##
## ================== ##
systemctl --user daemon-reload

# Verify if the quadlet it is generated correctly
/usr/libexec/podman/quadlet -dryrun -user

DevDorrejo avatar Oct 18 '25 20:10 DevDorrejo

don't know why you are building that bash script, I simply use a systemd service file and podman-compose to utilize the already existing docker-compose.yml files (either in multi-node or single-node dir):

[Unit]
Description=Podman-compose wazuh.service
Wants=network.target
After=network-online.target

[Service]
Type=oneshot
RemainAfterExit=true
WorkingDirectory=/opt/wazuh/wazuh-docker/multi-node
ExecStart=/home/nuvariant/.local/bin/podman-compose --in-pod=true up -d
ExecStop=/home/nuvariant/.local/bin/podman-compose down

[Install]
WantedBy=default.target

leonidas-o avatar Oct 19 '25 10:10 leonidas-o