Generate random passwords for the default API users (`wazuh`, `wazuh-wui`) at installation
Description
The manager ships two Server API users, and both are created with a password equal to their own username:
| User | ID | Password today | Consumer |
|---|---|---|---|
wazuh |
1 | wazuh |
Operators, scripts, API clients |
wazuh-wui |
2 | wazuh-wui |
The Wazuh dashboard, to reach the Server API on port 55000 |
Both are linked to the administrator role, the credentials are public (they are literals in
framework/wazuh/rbac/default/users.yaml:6,16 and are
documented in the installation guide), and the API starts and serves normally with them. Anyone
who can reach port 55000 on a fresh installation has full administrator access to the manager until
the operator changes the passwords by hand. The only current mitigation is a WARNING line at
startup (api/scripts/wazuh_manager_apid.py:97-112).
We want every installation to end up with unique, randomly generated, policy-compliant passwords for both users, disclosed to the operator in a secure way, and propagated to the Wazuh dashboard so that it keeps working out of the box — in all installation flows (packages, sources, installation assistant all-in-one, installation assistant distributed, containers, Kubernetes) and in all cluster topologies.
How it works today
rbac.db(/var/wazuh-manager/api/configuration/security/rbac.db) is not created by any installer. It is created and seeded lazily on the firstwazuh-manager-apidstart, bycheck_database_integrity()(framework/wazuh/rbac/orm.py:2461-2548) →DatabaseManager.insert_default_resources()(orm.py:2092-2099), which reads the plaintextpassword:values fromusers.yamland hashes them with Werkzeug scrypt.- No installation script in this repo generates any API credential. The only random-secret precedent is the cluster key at src/init/inst-functions.sh:995-999.
- The password policy (12–64 characters, at least one uppercase, one lowercase, one digit and one
symbol) is enforced only in framework/wazuh/security.py:23;
reserved users (ID ≤ 99) can only be modified by another reserved user
(security.py:232-240, error
5011). - The only first-party tooling to change them is
bin/rbac_control change-password(interactive prompt,--password-file,--passwords-file; never accepts a password as a command-line argument).rbac_control factory-resetdeletes the DB and restores the shipped defaults. wazuh-manager-apidruns on the master node only (src/init/wazuh-server.sh:193,392), andrbac.dbis not cluster-synchronized: every node keeps its own copy with the shipped defaults until it becomes master.- The dashboard holds the only external copy of the
wazuh-wuicredential, inwazuh_core.hosts.<host>.passwordinside/etc/wazuh-dashboard/opensearch_dashboards.yml, read once at startup (in-tree sample: api/tools/env/wazuh-dashboard/wazuh.dashboard.yml:44-54). Container images take it from theAPI_USERNAME/API_PASSWORDenvironment variables. wazuh-install.shandwazuh-passwords-tool.share not in this repository — they live inwazuh/wazuh-installation-assistant, which is where the all-in-one and distributed flows are orchestrated.
Proposed approach
Generate at RBAC seed time (framework). Remove the
password:keys fromusers.yaml(keepdescriptionandallow_run_as) and haveinsert_default_resources()produce a random password per default user through a new helper (e.g.generate_default_password()inframework/wazuh/core/security.py), built onsecretsand guaranteed to satisfy_user_password(security.py:23). Since seeding is lazy and centralized, this single code path covers every installation flow: DEB, RPM, sources, Docker, devcontainer and QA environments. No new hook is needed ininstall.sh,postinstor the RPM spec.Provisioning escape hatch. When a pre-seed credentials input is present at DB-creation time (a file under
api/configuration/security/, or an equivalent environment input), use those passwords instead of generating new ones. This is what lets the installation assistant, the container images and CI provision deterministic credentials non-interactively. Defining its exact path, format, ownership and lifetime is part of this issue.Disclose once, securely. Write the generated passwords to
WAZUH_HOME/api/configuration/security/wazuh-api-passwords.txtwith the most restrictive ownership and mode the apid runtime user allows (target0400), and log aWARNINGat first start pointing at that file and instructing the operator to read it and then delete it. The password itself must never appear in the manager log, in the installer output, or inps.Rework the default-password warning.
get_users_with_default_password()(framework/wazuh/core/security.py:137-150) andwarn_about_default_passwords()lose their meaning once there is no shipped password. Replace the check with "the generated-credentials file is still on disk", keeping the pointer torbac_control change-password.factory-resetmust not restore a known password.rbac_db_factory_reset()(core/security.py:125-134) has to regenerate and re-disclose, exactly like a fresh install.Cluster. Each node seeds its own
rbac.db, so with random generation a promoted worker would hold a different password and silently break the dashboard (3002 - Request failed with status code 401, then403aftermax_login_attempts). We do not add credential synchronization over the cluster protocol: instead, multi-node installations must push one common password to every node withrbac_control change-password --passwords-file, and this must be documented and verified, including that the tooling works on a node whose apid has never run.Dashboard and cross-repo wiring. The manager exposes the credentials (generated file) and accepts injected ones; the installation assistant reads them on an all-in-one host and injects/pushes them on a distributed deployment, then writes
wazuh_core.hosts.<host>.passwordin the dashboard configuration. Container and Kubernetes deployments keep usingAPI_USERNAME/API_PASSWORDand thewazuh-api-credSecret.
Installation cases that must be analysed and tested
- Packages: DEB fresh install, RPM fresh install, sources
install.sh. - Reinstall / upgrade over an existing
rbac.db: passwords must be preserved, never regenerated (DEB carries the file over inpreinst/postinst, RPM simply does not own it). - Topologies: single node; cluster master + worker; worker promoted to master.
- Installation assistant, all-in-one: manager + indexer + dashboard on the same host.
- Installation assistant, distributed: dashboard on a different host from the manager.
- Containers:
api/tools/env/docker-compose and the devcontainer e2e dashboard image (src/engine/tools/devContainer/e2e/wazuh-dashboard/Dockerfile:17-20), which today rely on the shipped defaults. - Kubernetes: dashboard fed from the
wazuh-api-credSecret. rbac_control factory-resetandrbac_control change-passwordin all of the above.
Impact on existing tests
Roughly fifteen suites hard-code these credentials and will break; they must be migrated to the new
provisioning mechanism. The main ones:
api/test/integration/common.yaml:14-17 (including the
base64 basic_auth / basic_auth_context blobs), the RBAC/security tavern suites, the env SQL
fixtures under api/test/integration/env/configurations/,
tests/integration/test_api/test_miscs/test_recursion.py:56-57,
tests/integration/test_api/test_miscs/test_run_as_chunked_body.py:58-59, and
api/tools/env/wazuh-dashboard/wazuh.dashboard.yml:52-53.
Tasks
Manager (this repository)
- Add a random password generator (
secrets-based) that always satisfies the API password policy at framework/wazuh/security.py:23, with unit tests asserting compliance and uniqueness across invocations. - Remove the plaintext
password:entries from framework/wazuh/rbac/default/users.yaml and generate the password per default user ininsert_default_resources()(orm.py:2092-2099). - Design and implement the pre-seed/injection input for automated provisioning (path, format, ownership, who writes it, when it is consumed and removed).
- Write the generated credentials to a restricted-permission file and log a
WARNINGpointing to it; guarantee the secret is never logged nor exposed through the process list. - Rework
get_users_with_default_password()andwarn_about_default_passwords()to warn about an undeleted credentials file instead of a shipped default password. - Make
rbac_db_factory_reset()regenerate and re-disclose new random passwords. - Verify
rbac_control change-passwordstill satisfies the reserved-user rule (5011) with the new flow, and that it can set credentials on a node whose apid has never started (worker); extend the tool if it cannot. - Confirm package upgrade paths preserve the credentials (DEB
preinst/postinstcopy ofrbac.db, RPM spec not owning the file) and that nothing regenerates on restart. - Migrate every test fixture and suite that hard-codes
wazuh:wazuh/wazuh-wui:wazuh-wui(taverncommon.yaml+ base64 blobs, env SQL fixtures,test_recursion.py,test_run_as_chunked_body.py,api/tools/env/wazuh-dashboard/wazuh.dashboard.yml, devcontainer e2e dashboard image) to the provisioning mechanism. - Add unit and API integration tests for: generation, disclosure file (content and permissions), credential injection, no-regeneration on restart, and factory-reset.
- Update the documentation:
docs/ref/getting-started/installation.md:189-271("Change the default API passwords") anddocs/ref/modules/server-api/authentication.md:42-91(default users, what a password change does, cluster/worker-promotion behaviour). - Add the
CHANGELOG.mdentry.
Cross-repository (open as linked child issues)
-
wazuh/wazuh-installation-assistant— consume or inject the credentials, writewazuh_core.hosts.<host>.passwordin the dashboard configuration, keepwazuh-passwords-tool.shworking, and report/store the generated passwords for both the all-in-one and the distributed flows. -
wazuh/wazuh-dashboard,wazuh/wazuh-docker,wazuh/wazuh-kubernetes— confirm the credential sources (opensearch_dashboards.yml,API_USERNAME/API_PASSWORD,wazuh-api-credSecret) still work and document the change.
Validation
- Execute the full installation-case matrix listed above and attach evidence per case.
- For every case, confirm that
wazuh:wazuhandwazuh-wui:wazuh-wuiare rejected with401, that the provisioned credential authenticates, and that the dashboard reaches the API.
Definition of Done
- No installation flow ends with a predictable API password; the default passwords are gone from the repository.
- Every fresh installation gets unique, policy-compliant passwords for
wazuhandwazuh-wui; restarts and package upgrades over an existingrbac.dbpreserve them;factory-resetproduces new random ones. - The generated passwords are disclosed exactly once, through a restricted-permission file, never
in logs, installer output or
ps, with a clear operator message on how to read, use and remove it. - The Wazuh dashboard authenticates against the Server API out of the box in all-in-one and in distributed installations; container and Kubernetes deployments keep working through their environment/Secret path.
- Automated and unattended provisioning (installation assistant, Docker, CI) can inject credentials non-interactively, without ever passing them on a command line.
- Cluster behaviour is documented and verified, including the worker-promotion scenario, with a documented procedure to align credentials across nodes.
- Every test suite that hard-coded the default credentials is updated and green in CI; new unit and integration tests cover generation, disclosure, injection, no-regeneration and factory-reset.
- E2E evidence attached for every case of the installation matrix (install output, credentials file
permissions,
401on the old defaults, successful dashboard login, manager logs). - Documentation updated and
CHANGELOG.mdentry added. - Cross-repository child issues opened, linked and resolved.
- Reviewed and merged into
5.0.0.
Source: wazuh/wazuh