Skip to content

Add Default Container Environment Variables + Wazuh Agent Installation#250

Open
cmyers-mieweb wants to merge 4 commits intomainfrom
cmyers_wazuh-int
Open

Add Default Container Environment Variables + Wazuh Agent Installation#250
cmyers-mieweb wants to merge 4 commits intomainfrom
cmyers_wazuh-int

Conversation

@cmyers-mieweb
Copy link
Collaborator

This pull request introduces a new system for managing default environment variables for containers, with a focus on supporting Wazuh agent enrollment out-of-the-box. It adds a UI for editing default environment variables, updates the backend and database schema to store them as structured data, and seeds initial Wazuh-related variables. Additionally, it integrates a first-boot Wazuh agent enrollment service into the base container image.

Default Container Environment Variables Management:

  • Adds a new UI section in settings/index.ejs for managing default environment variables, allowing users to add, edit, and remove variables with descriptions. These are injected into every new container. [1] [2]
  • Updates backend logic in settings.js to support storing and retrieving these variables as an array of {key, value, description} objects, with migration support for the old flat-object format. [1] [2] [3]
  • Changes the Settings.value database column type from STRING to TEXT to accommodate larger/structured data and updates the model accordingly. [1] [2]

Wazuh Agent Enrollment Integration:

  • Adds a database seeder to pre-populate default_container_env_vars with Wazuh agent variables (WAZUH_MANAGER, WAZUH_REGISTRATION_PASSWORD), including migration logic to merge with existing values.
  • Integrates a first-boot Wazuh agent enrollment system into the base container image:
    • Adds wazuh-enroll.sh script and wazuh-enroll.service systemd unit, which automatically enrolls the agent if the relevant environment variables are set, and securely cleans up credentials after enrollment. [1] [2] [3]

These changes make it easier to centrally manage environment variables for containers and enable secure, automated Wazuh agent enrollment for improved monitoring and security.

Introduce Wazuh agent first-boot enrollment and system-wide default container environment variables.

- create-a-container: Use Setting in container creation to load a JSON 'default_container_env_vars' and inject WAZUH_MANAGER (hostname from wazuh_api_url) and WAZUH_REGISTRATION_PASSWORD (if set). Merge priority: image defaults < system defaults < per-container values.
- UI: Add settings UI to manage default container env vars and Wazuh settings (API URL + enrollment password). Password is not echoed back; only an indicator is shown. Validate Wazuh API URL and serialize env var entries before saving to settings.
- Server: Expose wazuh_api_url to authenticated views so a sidebar link appears when configured.
- Images: Update base image Dockerfile to include and enable wazuh-enroll.service and wazuh-enroll.sh.
- New files: Add systemd unit (wazuh-enroll.service) and enrollment script (wazuh-enroll.sh) which installs the Wazuh agent, uses /etc/machine-id as agent name, optionally writes and then purges the enrollment password, waits for enrollment, and writes a sentinel to avoid rerunning.

These changes allow the cluster manager to automatically provide Wazuh connection details to new containers and offer a management UI for defaults and enrollment credentials.
Switch default_container_env_vars to an array of {key,value,description} while still supporting the old flat-object {KEY: value} format. create-container now parses either format (ignoring descriptions when injecting container envs), updates log messaging, and removes automatic Wazuh API/password injection. The settings router/read/save flow was updated to persist an array, convert legacy objects to the new shape, and build the saved entries from the form. The settings view adds a Description column and UI inputs for descriptions and removes the Wazuh integration form and header link. server.js no longer exposes the Wazuh URL middleware. A new seeder adds WAZUH_MANAGER and WAZUH_REGISTRATION_PASSWORD to default_container_env_vars if missing.
Update the Settings model to use DataTypes.TEXT for the value field and add a Sequelize migration (create-a-container/migrations/20260320000000-change-settings-value-to-text.js) to alter the existing column to TEXT. The migration includes an up (to TEXT) and down (back to STRING) to allow storing longer setting values while preserving rollback capability.
# after a successful enrollment. If WAZUH_MANAGER is not set when the service
# runs, the script exits without creating the sentinel so that the service will
# attempt enrollment again after a container reconfigure adds the variable.
ConditionPathExists=!/etc/wazuh-enrolled
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should use a semaphore file like this. I'd prefer to use ConditionFirstBoot, but if there's a solid excuse to do it based on a different condition we should use a file like /var/ossec/etc/client.keys instead of this dedicated file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced the dedicated /etc/wazuh-enrolled sentinel with ConditionPathExists=!/var/ossec/etc/client.keys instead of ConditionFirstBoot because client.keys is the actual enrollment artifact created by Wazuh upon successful registration. This means the service will correctly reattempt enrollment on subsequent boots if a previous attempt failed, or if WAZUH_MANAGER was added later via a container reconfigure, whereas ConditionFirstBoot would only ever run once regardless of whether enrollment succeeded.

# the environment to this unit.
set -a
# shellcheck source=/dev/null
[ -f /etc/environment ] && source /etc/environment
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work because /etc/environment isn't a shell file, it's a KEY=value pairing. Either way, that doesn't matter because the service file is using it as an EnvironmentFile so we don't need to also source it here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service now gets env vars directly from systemd rather than sourcing the file in the script

Comment on lines +34 to +56
# ---------------------------------------------------------------------------
# Add Wazuh apt repository
# ---------------------------------------------------------------------------
curl -fsSL https://packages.wazuh.com/key/GPG-KEY-WAZUH \
| gpg --dearmor -o /usr/share/keyrings/wazuh.gpg

echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" \
> /etc/apt/sources.list.d/wazuh.list

# Update only the Wazuh source list to avoid touching unrelated repositories
apt-get update \
-o Dir::Etc::sourcelist="/etc/apt/sources.list.d/wazuh.list" \
-o Dir::Etc::sourceparts="-" \
-o APT::Get::List-Cleanup="0"

# ---------------------------------------------------------------------------
# Install the agent.
# The WAZUH_MANAGER and WAZUH_AGENT_NAME variables are consumed by the
# wazuh-agent dpkg post-install script to write ossec.conf automatically.
# ---------------------------------------------------------------------------
WAZUH_MANAGER="${WAZUH_MANAGER}" \
WAZUH_AGENT_NAME="${AGENT_NAME}" \
apt-get install -y --no-install-recommends wazuh-agent
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want all of this in the Dockerfile not in here. Having to install software at startup is more fragile and harder to control. Obviously, we won't have the WAZUH_MANAGER and other env vars so the postinst script from the Debian package won't enroll the agent, but that the point of the rest of this script.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved most of what was possible to the Dockerfile, anything that is dependent on the env vars remained

Comment on lines +98 to +106
if [ -f /var/ossec/etc/authd.pass ]; then
rm -f /var/ossec/etc/authd.pass
echo "Removed enrollment password from /var/ossec/etc/authd.pass"
fi

if [ -f /etc/environment ]; then
sed -i '/^WAZUH_REGISTRATION_PASSWORD=/d' /etc/environment
echo "Purged WAZUH_REGISTRATION_PASSWORD from /etc/environment"
fi
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this doesn't really matter. Even on removing the password from the /etc/environment, it still exists in /proc/1/environ (which is where /etc/environment is populated from) and in the LXC config. Although it's a "password" my understanding was it can only be used to register new hosts so I don't think its necessary to try hiding it like this. If I'm wrong and we do need to protect it we should discuss a different strategy for registration.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed password cleanup, since registration password also exists in /proc/1/environ and LXC config

Comment on lines +71 to +74
systemctl daemon-reload
systemctl enable wazuh-agent
systemctl start wazuh-agent

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this happen in the apt post-install script? Do we have to do it here too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed these since the package would be built into the image

curl -fsSL https://packages.wazuh.com/key/GPG-KEY-WAZUH \
| gpg --dearmor -o /usr/share/keyrings/wazuh.gpg

echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in deb822 format rather than list format (new Debian 13 standard), but also see other note about putting it in the Dockerfile.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wazuh agent now installed at build time and adds the apt repo in deb822 .sources format, installs wazuh-agent, then cleans up so no network needed at first boot

Comment on lines +38 to +41
mkdir -p /etc/systemd/system/multi-user.target.wants && \
ln -sf /etc/systemd/system/wazuh-enroll.service \
/etc/systemd/system/multi-user.target.wants/wazuh-enroll.service

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to do this to enable it. The template auto-enables all service files. If for some reason this is an exception, systemctl enable wazuh-enroll.service will work in a Dockerfile even though systemd isn't running.

Comment on lines +22 to +29
if (settings.default_container_env_vars) {
const parsed = JSON.parse(settings.default_container_env_vars);
if (Array.isArray(parsed)) {
defaultContainerEnvVars = parsed;
} else if (typeof parsed === 'object' && parsed !== null) {
defaultContainerEnvVars = Object.entries(parsed).map(([key, value]) => ({ key, value, description: '' }));
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're doing this sort of thing in two places (here any create-a-container/bin/create-container.js) should we abstract it into a method on the Settings model?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I built a new getDefaultContainerEnvVars() static method to abstract the JSON parsing and legacy format migration into one place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants