Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions .github/.goss.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,11 @@ file:
group: wazuh-manager
filetype: file
contains: []
/var/wazuh-manager/etc/sslmanager.cert:
/var/wazuh-manager/etc/certs:
exists: true
mode: "0644"
owner: root
group: root
filetype: file
contains: []
/var/wazuh-manager/etc/sslmanager.key:
exists: true
mode: "0600"
owner: root
group: root
filetype: file
contains: []
group: wazuh-manager
filetype: directory
user:
wazuh-manager:
exists: true
Expand Down
31 changes: 30 additions & 1 deletion .github/workflows/5_check_integration_tools.yml
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ jobs:
set -e
cd /tmp/wazuh-docker/${DEPLOYMENT}
echo '=== Running certificate generation ==='
sudo bash /tmp/wazuh-docker/tools/utils/deployment/certificates-conf.sh --cert --copy
sudo bash /tmp/wazuh-docker/tools/utils/deployment/certificates-conf.sh --cert --copy --priv
echo ''
echo '=== Generated certificate files ==='
find ./config -name '*.pem' | sort
Expand Down Expand Up @@ -675,6 +675,35 @@ jobs:
exit 1
"

- name: Verify manager certificates are unique per container
run: |
DEPLOYMENT="${{ matrix.deployment_type }}"
ssh ${{ env.SSH_OPTS }} "${{ env.REMOTE }}" "
set -eo pipefail
cd /tmp/wazuh-docker/${DEPLOYMENT}

rm -f /tmp/remoted-fingerprints.txt
for SVC in \$(sudo docker compose config --services | grep -E 'wazuh\.(manager|master|worker)'); do
echo \"=== \$SVC ===\"
sudo docker compose exec -T \$SVC ls -ld /var/wazuh-manager/etc/certs
sudo docker compose exec -T \$SVC ls -la /var/wazuh-manager/etc/certs
sudo docker compose exec -T \$SVC openssl x509 \
-in /var/wazuh-manager/etc/certs/remoted.pem -noout -subject -dates
sudo docker compose exec -T \$SVC openssl x509 \
-in /var/wazuh-manager/etc/certs/remoted.pem -noout -fingerprint \
| tee -a /tmp/remoted-fingerprints.txt
done

# Every manager node must hold its own self-signed certificate
DUPLICATED=\$(sort /tmp/remoted-fingerprints.txt | uniq -d)
if [ -n \"\$DUPLICATED\" ]; then
echo 'ERROR: the same remoted certificate is shared across manager nodes'
echo \"\$DUPLICATED\"
exit 1
fi
echo 'OK: each manager node has its own remoted certificate'
"

- name: Cluster warm-up wait
run: |
if [ "${{ matrix.deployment_type }}" = "multi-node" ]; then
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
| Issue | Comment |
| - | - |
| [#2461](https://github.com/wazuh/wazuh-docker/issues/2461) | Added explicit `permissions` blocks to the 4.x workflows to restrict the `GITHUB_TOKEN` scope |
| [#2601](https://github.com/wazuh/wazuh-docker/issues/2601) | Regenerate the manager self-signed server certificate per container at first boot |

### Removed

Expand Down
7 changes: 5 additions & 2 deletions build-docker-images/wazuh-manager/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ RUN dnf install openssl findutils procps shadow-utils -y && \
mkdir -p /var/wazuh-manager/etc/certs && \
chown root:wazuh-manager /var/wazuh-manager/etc/certs && \
chmod 1770 /var/wazuh-manager/etc/certs && \
rm -f /var/wazuh-manager/etc/sslmanager.key && \
rm -f /var/wazuh-manager/etc/sslmanager.cert
# Remove manager self-signed certificate/key
rm -f /var/wazuh-manager/etc/certs/remoted.pem \
/var/wazuh-manager/etc/certs/remoted-key.pem \
/var/wazuh-manager/etc/certs/apid.pem \
/var/wazuh-manager/etc/certs/apid-key.pem

# Prepare permanent data snapshot (sync calls: https://github.com/docker/docker/issues/9547)
RUN chmod 755 /permanent_data.sh && \
Expand Down
103 changes: 93 additions & 10 deletions build-docker-images/wazuh-manager/config/etc/cont-init.d/0-wazuh-init
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ mount_permanent_data() {
# Check if the path is not empty
if find ${permanent_dir} -mindepth 1 | read; then
print "The path ${permanent_dir} is already mounted"
elif [ ! -d "${data_tmp}" ]; then
print "No permanent data snapshot for ${permanent_dir}, skipping"
else
print "Installing ${permanent_dir}"
exec_cmd "cp -ar ${data_tmp}. ${permanent_dir}"
Expand Down Expand Up @@ -117,12 +119,93 @@ remove_data_files() {

##############################################################################
# Create certificates: Manager
#
# The manager has a single self-signed server pair, etc/certs/remoted.*, read
# both by remoted's HTTPS agent listener (<remote><https>) and by authd
# (<auth><ssl_manager_cert>). The package post-install generates it, so the image
# build removes it (see Dockerfile) and every container generates its own here:
# no two containers from the same image share a private key.
#
# Both consumers are read from the configuration and generated independently, so
# a deployment pointing one of them at a custom path still gets a valid pair.
#
# remoted opens these files after dropping privileges and does not start if they
# are missing or unreadable, so this must run before the manager is started and
# the files must end up wazuh-manager:wazuh-manager 0640.
##############################################################################

create_wazuh_key_cert() {
print "Creating wazuh-authd key and cert"
exec_cmd "openssl genrsa -out ${WAZUH_INSTALL_PATH}/etc/sslmanager.key 4096"
exec_cmd "openssl req -new -x509 -key ${WAZUH_INSTALL_PATH}/etc/sslmanager.key -out ${WAZUH_INSTALL_PATH}/etc/sslmanager.cert -days 3650 -subj /CN=${HOSTNAME}/"
# Certificate paths in the configuration are Wazuh-home relative, as the rest of
# the manager configuration is.
resolve_wazuh_path() {
case "$1" in
/*) echo "$1" ;;
*) echo "${WAZUH_INSTALL_PATH}/$1" ;;
esac
}

# $1: XML tag, $2: enclosing block
get_conf_path() {
sed -n "/<$2>/,/<\/$2>/s|.*<$1>\(.*\)</$1>.*|\1|p" \
"${WAZUH_INSTALL_PATH}/etc/wazuh-manager.conf" | head -n1
}

# $1: certificate path, $2: key path
create_server_cert_pair() {
local cert=$(resolve_wazuh_path "$1")
local key=$(resolve_wazuh_path "$2")

# Mixed-ownership directory: the daemons regenerate their own certificates here
# (group write), while the sticky bit keeps them from replacing the root-owned
# indexer trust material that shares the directory. Ownership and mode are
# best-effort: the directory may be a read-only mount, and failing to re-apply
# them must not stop the certificate from being created.
exec_cmd "mkdir -p $(dirname "${cert}") $(dirname "${key}")"
chown root:wazuh-manager "$(dirname "${cert}")" 2>/dev/null || true
chmod 1770 "$(dirname "${cert}")" 2>/dev/null || true

if [ -e "${cert}" ] && [ -e "${key}" ]
then
print "Manager server certificate ${cert} already present, skipping generation"
elif [ -e "${cert}" ] || [ -e "${key}" ]
then
# Same guard as the package post-install: never overwrite operator-provided
# material. Half a pair cannot be completed, so warn instead of guessing.
print "Warning: only one of ${cert} / ${key} is present; remoted will not start"
return
else
print "Creating the manager server certificate and key (${cert})"
${WAZUH_INSTALL_PATH}/bin/wazuh-manager-remoted -C 3650 -B 4096 \
-S "/C=US/ST=California/CN=Wazuh/" -K "${key}" -X "${cert}" > /dev/null 2>&1 || \
exec_cmd "openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-keyout ${key} -out ${cert} -subj /C=US/ST=California/CN=Wazuh/"
fi

# remoted and authd read these after dropping privileges. Not fatal on failure:
# an operator-provided certificate may be a read-only bind mount.
chown wazuh-manager:wazuh-manager "${cert}" "${key}" 2>/dev/null || \
print "Warning: could not set ownership on ${cert} (read-only mount?)"
chmod 640 "${cert}" "${key}" 2>/dev/null || true
}

create_manager_server_certs() {
local https_cert=$(get_conf_path certificate https)
local https_key=$(get_conf_path key https)
local authd_cert=$(get_conf_path ssl_manager_cert auth)
local authd_key=$(get_conf_path ssl_manager_key auth)

# The manager has one self-signed pair and one identity: authd's
# <ssl_manager_cert> is derived from the HTTPS listener's <certificate> by the
# installation itself, and /enroll's mTLS mode treats that certificate as the
# enrollment credential. Only that pair is generated, exactly as the package
# post-install does; a mismatch can only come from hand-edited configuration.
create_server_cert_pair "${https_cert:-etc/certs/remoted.pem}" \
"${https_key:-etc/certs/remoted-key.pem}"

if [ "${authd_cert:-etc/certs/remoted.pem}" != "${https_cert:-etc/certs/remoted.pem}" ] || \
[ "${authd_key:-etc/certs/remoted-key.pem}" != "${https_key:-etc/certs/remoted-key.pem}" ]
then
print "Warning: authd (${authd_cert}) and the HTTPS listener (${https_cert}) use different certificates; both must present the same manager identity"
fi
}

##############################################################################
Expand Down Expand Up @@ -277,12 +360,6 @@ main() {
# Remove some files in permanent_data (i.e. .template.db)
remove_data_files

# Create wazuh-authd key and cert if not present
if [ ! -e ${WAZUH_INSTALL_PATH}/etc/sslmanager.key ]
then
create_wazuh_key_cert
fi

# Mount selected files (WAZUH_CONFIG_MOUNT) to container
mount_files

Expand All @@ -291,6 +368,12 @@ main() {

# Configure wazuh-manager.conf based on environment variables
function_configure_wazuh_manager_conf

# Create the manager server certificates if not present. After mount_files and
# the configuration rewrite, so a mounted configuration with custom certificate
# paths is honoured and a mounted certificate is detected as already present.
create_manager_server_certs

# Delete temporary data folder
rm -rf ${WAZUH_INSTALL_PATH}/data_tmp

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ function_wazuh_migration(){
chown wazuh-manager:wazuh-manager /var/wazuh-manager/etc/client.keys
chmod 640 /var/wazuh-manager/etc/client.keys

\cp -f /wazuh-migration/data/etc/sslmanager.cert /var/wazuh-manager/etc/sslmanager.cert
\cp -f /wazuh-migration/data/etc/sslmanager.key /var/wazuh-manager/etc/sslmanager.key
chown root:root /var/wazuh-manager/etc/sslmanager.cert /var/wazuh-manager/etc/sslmanager.key
chmod 640 /var/wazuh-manager/etc/sslmanager.cert /var/wazuh-manager/etc/sslmanager.key

\cp -f /wazuh-migration/data/etc/shared/default/agent.conf /var/wazuh-manager/etc/shared/default/agent.conf
chown wazuh-manager:wazuh-manager /var/wazuh-manager/etc/shared/default/agent.conf
chmod 660 /var/wazuh-manager/etc/shared/default/agent.conf
Expand Down
6 changes: 6 additions & 0 deletions docs/ref/configuration/configuration-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ To persist files or directories in your Wazuh deployment, you can mount them as

> **Important**: Ensure that files exist on the host before starting the containers. If the file doesn't exist, Docker will create a directory instead, which may cause startup failures.

### Wazuh manager self-signed certificate

The `docker-compose.yml` files mount a named volume on `/var/wazuh-manager/etc` (`wazuh_etc` in single-node; `master-wazuh-etc` and `worker-wazuh-etc` in multi-node). That volume holds `certs/remoted.pem` and `certs/remoted-key.pem`, the self-signed pair each manager container generates on its first start and reuses on every later start. It is used by the HTTPS agent listener and by agent enrollment (`authd`), and it is unique per deployment and per cluster node.

Removing the volume (for example, with `docker compose down -v`) deletes the pair, and the next start generates a new one. Agents do not validate this certificate by default, so a new pair does not break already enrolled agents. Do not copy the volume between deployments: that reuses the same private key in both. See [Security](../security.md) for rotation and for using your own certificate.

### Wazuh Dashboard keystore

The `docker-compose.yml` files mount the named volume `wazuh-dashboard-config` on `/usr/share/wazuh-dashboard/config`, which is where `opensearch_dashboards.keystore` is stored. Keeping this volume preserves the `wazuh_ai_assistant.encryptionKey` generated on the first start.
Expand Down
2 changes: 1 addition & 1 deletion docs/ref/integration_test/docker_integration_tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ Runs on the **runner** (not the VM):

3. **Copy `wazuh-docker/` to VM** via SCP: `scp -r wazuh-docker {remote}:/tmp/wazuh-docker`

4. **Generate certificates on VM**: runs `tools/utils/deployment/certificates-conf.sh --cert --copy` inside `/tmp/wazuh-docker/{deployment}/`
4. **Generate certificates on VM**: runs `tools/utils/deployment/certificates-conf.sh --cert --copy --priv` inside `/tmp/wazuh-docker/{deployment}/`

#### Deployment

Expand Down
14 changes: 14 additions & 0 deletions docs/ref/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ This section summarizes security recommendations for Wazuh Docker deployments (s
- Regenerate certificates if private keys are leaked or if nodes are re-provisioned.
- Use certificates and TLS settings appropriate for production (trusted CA, correct DNS names, and key protection).

### Manager self-signed certificate

The Wazuh manager image ships no self-signed certificate. Each manager container generates its own `etc/certs/remoted.pem` and `etc/certs/remoted-key.pem` on its first start, so no two containers share a private key. That pair serves both the HTTPS agent listener and the agent enrollment service (`authd`), and it is stored in the manager `etc` volume (`wazuh_etc` in single-node; `master-wazuh-etc` and `worker-wazuh-etc` in multi-node), which keeps it stable across restarts and container recreation.

- Do not copy or share the manager `etc` volume between deployments, and do not bake the pair into a derived image: either reintroduces a shared private key.
- To rotate the certificate, remove both files and restart the manager. The next start generates a new pair:
```bash
docker compose exec wazuh.manager rm -f /var/wazuh-manager/etc/certs/remoted.pem \
/var/wazuh-manager/etc/certs/remoted-key.pem
docker compose restart wazuh.manager
```
- To use your own certificate instead, mount the pair at those paths owned by `101:101` (`wazuh-manager:wazuh-manager`) with mode `640`. The container detects it and does not overwrite it. The manager opens these files after dropping privileges, so any other ownership prevents the HTTPS listener from starting.
- The Wazuh API certificate (`etc/certs/apid.pem` and `apid-key.pem`) is generated by the API on its first start, and is also unique per container.

## Network exposure

- Restrict access to exposed service ports at the host firewall and security group level.
Expand Down
14 changes: 14 additions & 0 deletions docs/ref/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,17 @@ Below is a step-by-step example of how to perform this update:
```bash
docker-compose up -d
```

## Manager self-signed certificate on existing deployments

Manager images built before the per-container certificate change shipped `etc/certs/remoted.pem` and `etc/certs/remoted-key.pem` inside the image, so the pair was copied into the manager `etc` volume the first time the deployment started and is the same in every deployment created from that image.

Upgrading the image tag does not replace it: the volume already holds a pair, and the container never overwrites an existing one. To move an existing deployment onto a certificate of its own, remove both files and restart the manager after the upgrade:

```bash
docker compose exec wazuh.manager rm -f /var/wazuh-manager/etc/certs/remoted.pem \
/var/wazuh-manager/etc/certs/remoted-key.pem
docker compose restart wazuh.manager
```

In multi-node, repeat it for `wazuh.master` and `wazuh.worker`. Agents do not validate this certificate by default, so the rotation does not require any change on the agents.