Getting Started

This guide walks you through setting up and running the wazuh-cert-oauth2 project locally. There are two ways to run the stack — pick the one that fits your workflow:

GitHub Integration: The Webhook Proxy supports automated GitHub issue creation for new user registrations in Keycloak. To use this feature, you will need a GitHub Personal Access Token (preferably a fine-grained token with only Issue Creation permissions), the repository owner, and the repository name. See the Configuration Reference for details.

Prerequisites

  Option A: Docker Compose Option B: From Source
Best for Quick setup, testing, demos Active development, debugging
Requires Docker & Docker Compose Rust toolchain + build deps
Services managed Automatically Manually (3 separate processes)

Want to understand how the components fit together first? See the Architecture Overview.


Prerequisites

For Docker Compose (Option A)

  • Docker & Docker Compose: Version 2.x or higher.
  • Git: For cloning the repository.
  • OpenSSL: To generate the Root CA.

For From Source (Option B)

Everything above, plus:

  • Rust & Cargo: Latest stable version — install via rustup.rs.
  • Build dependencies:
    • openssl
    • musl-tools
    • build-essential
    • pkg-config
    • perl

Step 1: Generate the Root CA

This is required regardless of which option you choose. The Root CA is used to sign agent certificates.

Run these commands from the repository root:

openssl genrsa -out root-ca-key.pem 2048
openssl req -days 3650 -new -x509 -sha256 \
    -key root-ca-key.pem \
    -out root-ca.pem \
    -subj "/C=DE/L=Bayern/O=Adorsys/CN=root-ca"

You should now have root-ca.pem and root-ca-key.pem in the repository root. Both options below depend on these files.


The fastest way to get the full stack running. Docker Compose starts the Certificate Server, Webhook Proxy, and Keycloak together.

docker compose up -d --build

On Linux, if your shell doesn’t automatically export UID/GID, prefix the command to avoid permission issues with the mounted CA files:

UID=$(id -u) GID=$(id -g) docker compose up -d --build

Wait about 30–60 seconds for Keycloak to fully boot. You can monitor progress with:

docker compose logs -f keycloak

Running Services

Service URL Credentials
Keycloak Admin Console http://localhost:9100/admin admin / password
Certificate Server API http://localhost:8000
Webhook Proxy http://localhost:8100

The Certificate Server and Webhook Proxy are used internally by the stack components and don’t require direct interaction during normal usage.

Enroll an Agent

With the stack running, use the client binary to enroll an agent. The Docker Compose setup pre-configures a test user and client in Keycloak:

  • Test user: test / test
  • OAuth2 client ID: test-client (public client, no secret needed)

Run the client from the repository root:

./target/release/wazuh-cert-oauth2-client oauth2 \
  --issuer http://localhost:9100/realms/dev \
  --client-id test-client \
  --endpoint http://localhost:8000/api/register-agent

If you haven’t built the binaries yet, run cargo build --release first.

The client will attempt to open the authorization URL in your system’s default browser automatically. If that fails, the URL will be printed in the terminal for you to open manually. Log in with test / test, and paste the authorization code back into the terminal. On success, the signed certificate and private key will be written to the platform-specific default path.


Option B: Running from Source

Run each component individually — useful when you’re actively developing or need to attach a debugger.

1. Build

cargo build --release

2. Start Keycloak (via Docker)

The server and webhook need an OIDC provider. The easiest way is to spin up just Keycloak from the Compose file:

docker compose up -d keycloak keycloak-config download-theme

Wait for Keycloak to be ready at http://localhost:9100/admin (creds: admin / password).

3. Run the Certificate Server

export RUST_LOG=info,rocket=warn,reqwest=warn

./target/release/wazuh-cert-oauth2-server \
  --oauth-issuer http://localhost:9100/realms/dev \
  --root-ca-path ./root-ca.pem \
  --root-ca-key-path ./root-ca-key.pem

Server listens on http://localhost:8000.

4. Run the Webhook Proxy

In a separate terminal:

export RUST_LOG=info,rocket=warn,reqwest=warn

./target/release/wazuh-cert-oauth2-webhook \
  --server-base-url http://localhost:8000 \
  --oauth-issuer http://localhost:9100/realms/dev \
  --oauth-client-id test-client-secret \
  --oauth-client-secret some-secret

Webhook proxy listens on http://localhost:8100.

5. Enroll an Agent

With the server and Keycloak running, enroll an agent using the pre-configured test client:

export RUST_LOG=info,reqwest=warn

./target/release/wazuh-cert-oauth2-client oauth2 \
  --issuer http://localhost:9100/realms/dev \
  --client-id test-client \
  --endpoint http://localhost:8000/api/register-agent

The client will attempt to open the authorization URL in your system’s default browser automatically. If that fails, the URL will be printed in the terminal for you to open manually. Log in with test / test, and paste the authorization code back into the terminal.


Configuration Reference

Server Flags

Flag Env Variable Default Purpose
--oauth-issuer OAUTH_ISSUER (Required) OIDC issuer URL.
--root-ca-path ROOT_CA_PATH (Required) Path to Root CA cert (PEM).
--root-ca-key-path ROOT_CA_KEY_PATH (Required) Path to Root CA key (PEM).
--crl-path CRL_PATH /data/issuing.crl Path where CRL is written.
--ledger-path LEDGER_PATH /data/ledger.csv CSV ledger path (local-dev fallback).
--database-url DATABASE_URL (Optional) PostgreSQL DSN. When set, the ledger uses PostgreSQL as the system of record; otherwise it falls back to the CSV ledger at LEDGER_PATH.
--webhook-base-url WEBHOOK_BASE_URL (Optional) Base URL of the Webhook Proxy.
--webhook-bearer-token WEBHOOK_BEARER_TOKEN (Optional) Bearer token for the Webhook Proxy.

Webhook Flags

Breaking change: WAZUH_API_TLS_VERIFY now defaults to true. If your Wazuh Manager uses a self-signed certificate and you do not have WAZUH_API_CA_BUNDLE configured, set WAZUH_API_TLS_VERIFY=false before upgrading to avoid eviction failures.

Flag Env Variable Default Purpose
--server-base-url SERVER_BASE_URL (Required) Base URL of the Certificate Server.
--spool-dir SPOOL_DIR /data/spool Directory for persistent retry spooling.
--spool-evict-ttl-secs SPOOL_EVICT_TTL_SECS 86400 (24h) Maximum age (in seconds) an eviction request stays in the spool before being moved to the dead-letter directory. Increase for environments prone to longer Wazuh outages.
--spool-dead-letter-dir SPOOL_DEAD_LETTER_DIR dead-letter/ sibling of SPOOL_DIR Directory where expired eviction spool items are quarantined for operator inspection/replay. Must not be the same as SPOOL_DIR. Must live on the same filesystem/volume as SPOOL_DIR
--oauth-client-id OAUTH_CLIENT_ID (Required) Client ID to talk to the Server.
--oauth-client-secret OAUTH_CLIENT_SECRET (Required) Client Secret for the Server.
--github-token GITHUB_TOKEN (Optional) GitHub PAT for issue creation.
--github-repo-owner GITHUB_REPO_OWNER (Optional) Owner of the repo for tickets.
--github-repo-name GITHUB_REPO_NAME (Optional) Name of the repo for tickets.
--keycloak-admin-base-url KEYCLOAK_ADMIN_BASE_URL (Optional) Keycloak Admin API URL.
--wazuh-manager-url WAZUH_MANAGER_URL (Optional) Wazuh Manager API URL.
--wazuh-api-user WAZUH_API_USER (Optional) Wazuh API user.
--wazuh-api-password WAZUH_API_PASSWORD (Optional) Wazuh API password.
--wazuh-api-token WAZUH_API_TOKEN (Optional) Wazuh API static token.
--wazuh-eviction-grace-seconds WAZUH_EVICTION_GRACE_SECONDS 30 Grace period before agent deletion (skipped for auto-rotate).
--wazuh-api-tls-verify WAZUH_API_TLS_VERIFY true Enable TLS cert verification for the Wazuh Manager API. Set to false only for testing or self-signed certificates without a configured CA bundle.
--wazuh-api-ca-bundle WAZUH_API_CA_BUNDLE (Optional) Path to a PEM CA bundle for the Wazuh Manager API.

Client Flags

Flag Env Variable Default Purpose
--issuer ISSUER Keycloak URL OIDC issuer for agent auth.
--client-id CLIENT_ID adorsys-machine-client Agent’s OAuth2 client ID.
--endpoint ENDPOINT registration URL Server endpoint for CSR submission.
--cert-path CERT_PATH (Platform specific) Destination for the signed cert.

Troubleshooting & Debugging

🌐 Connectivity Issues

“error sending request for url” (Client)

Symptom: The client fails with an error similar to:
An error occurred during execution: HTTP error: error sending request for url (http://localhost:9100/realms/dev/.well-known/openid-configuration)

Cause: The OIDC provider (Keycloak) is not reachable from the client’s network.
Solution:

  • Verify you can access http://localhost:9100/realms/dev/.well-known/openid-configuration in your browser.
  • Ensure the --issuer URL matches the reachable address of your OIDC provider.

401 Unauthorized or “Could not get JWKS” (Server)

Symptom: Server logs show Could not get JWKS HTTP error: error sending request for url (...) or the client receives a 401.

Cause: The Certificate Server cannot reach the OIDC issuer to validate tokens.
Solution: When running in Docker, services must use internal service names. Ensure OAUTH_ISSUER in compose.yaml uses http://keycloak:9100/... instead of localhost.


🔑 Permissions & Security

“Permission Denied (os error 13)”

Symptom: CSR signing failed: I/O error: Permission denied (os error 13) appears in logs.

Cause: The container user cannot read the host-mounted Root CA files.
Solution:

  • Linux/macOS: Run with explicit UID/GID: UID=$(id -u) GID=$(id -g) docker compose up -d
  • Windows (Docker Desktop): Ensure Docker Desktop has permission to access the repository folder and the files aren’t blocked by Windows Security.

“Executable file not found” (docker exec)

Symptom: OCI runtime exec failed: ... exec: "ls": executable file not found in $PATH

Cause: The project uses Distroless images — no shell or standard utilities.
Solution:

  1. Use docker compose logs -f <service> for debugging.
  2. To inspect files, use the ubuntu sidecar service:
    docker compose exec ubuntu ls -R /data
    

⚙️ Infrastructure & Setup

keycloak-config “Restarting”

Symptom: The keycloak-config container shows Restarting for the first minute.
Cause: It tries to configure Keycloak before it’s fully booted.
Solution: No action needed — it will succeed once Keycloak is ready.

Version/Help Flags not working

Note: If help and version flags don’t exit correctly, rebuild the binaries with cargo build --release.


Nginx Sidecar Image

The project builds a separate Docker image for an nginx sidecar that validates agent certificates against a CRL before proxying enrollment traffic to the Wazuh manager’s authd.

Building the Sidecar Image

From the repository root:

docker build -f .docker/nginx-sidecar/Dockerfile -t nginx-sidecar:local .docker/nginx-sidecar/

This produces an image based on nginx:alpine with curl, openssl, and gettext (for envsubst) installed. Stock nginx:alpine does not work because it lacks these dependencies.

How It Works

  1. entrypoint.sh uses envsubst to render nginx.conf.template/etc/nginx/nginx.conf
  2. Runs an initial CRL fetch via fetch-crl.sh (bypasses If-None-Match to avoid long-poll delay on startup)
  3. Starts a background CRL refresh loop (long-polling with ETag support)
  4. Launches nginx in the foreground

Environment Variables

Variable Default Purpose
LISTEN_PORT 1515 Port for agent mTLS connections
AUTHD_UPSTREAM_HOST 127.0.0.1 Wazuh manager authd host
AUTHD_UPSTREAM_PORT 15151 Wazuh manager authd port
SSL_CERT_PATH /etc/ssl/certs/server.pem Server certificate (PEM)
SSL_KEY_PATH /etc/ssl/certs/server-key.pem Server private key (PEM)
SSL_CA_PATH /etc/ssl/certs/ca.pem CA certificate for client verification
CRL_FILE /etc/nginx/crl/crl.pem Path to the CRL file (PEM)
CRL_ENABLED true Enable/disable CRL validation
CRL_URL (required when CRL enabled) cert-server CRL endpoint URL
CRL_REFRESH_INTERVAL 300 Seconds between CRL refresh retries on error
CURL_TIMEOUT 35 Curl timeout (must exceed server long-poll timeout)
LOG_LEVEL debug nginx error log level
WORKER_CONNECTIONS 1024 nginx worker_connections

Copyright © 2026 ADORSYS-GIS. Distributed under the MIT license.