#!/usr/bin/env bash
# Skeleton: onboard one tenant onto a multi-tenant Wazuh 5.0 deployment.
#
# Companion to: https://maheshtata.com/wazuh/wazuh-multi-tenant/
#
# This is a SKELETON, not a finished tool. It performs the manager-side steps
# that are fully documented for 5.0-beta and stops at every step that depends
# on your naming, your secret store or an undocumented API shape. Those stops
# are marked TODO and are deliberate: a half-guessed security role is worse
# than a manual one.
#
# What it does:
#   1. Authenticate to the manager API and hold a JWT.
#   2. Create the tenant's agent group (must exist BEFORE any agent enrols).
#   3. Seed the group's shared agent.conf, if one is supplied.
#   4. Mint a time-limited enrolment token for the rollout.
#   5. Reconcile: list the group's agents and any agent with no group.
#
# What it does NOT do, on purpose:
#   - Create the indexer security role (see tenant-role.json).
#   - Create the role mapping (see tenant-rolesmapping.yml).
#   - Create the manager API policy (see tenant-api-policy.json).
#   - Store or print secrets anywhere other than stdout, once.
#
# Usage:
#   WAZUH_API=https://manager.example.com:55000 \
#   WAZUH_API_USER=onboarding \
#   WAZUH_API_PASS='...' \
#   TENANT=acmeretail \
#   MANAGER_ADDRESS=wazuh-manager.example.com \
#   SHARED_CONF=./agent.conf \
#     ./onboard-tenant.sh

set -euo pipefail

WAZUH_API="${WAZUH_API:?set WAZUH_API, e.g. https://manager.example.com:55000}"
WAZUH_API_USER="${WAZUH_API_USER:?set WAZUH_API_USER}"
WAZUH_API_PASS="${WAZUH_API_PASS:?set WAZUH_API_PASS}"
TENANT="${TENANT:?set TENANT, the short tenant identifier}"

# Tenant group naming. Keep it stable: renaming later means recreating the
# group, moving every agent and rewriting the indexer security role.
# Allowed characters in a group name: letters, digits, dots, underscores,
# hyphens. See the hyphen warning in tenant-role.json before choosing a
# separator.
GROUP="${GROUP:-t-${TENANT}}"

# Enrolment token settings. `address` MUST be one of the names present in the
# manager's HTTPS listener certificate (remote.https.certificate), or agents
# will fail to verify the manager.
MANAGER_ADDRESS="${MANAGER_ADDRESS:-}"
TOKEN_TTL="${TOKEN_TTL:-12h}"
TOKEN_MAX_USES="${TOKEN_MAX_USES:-50}"

# Optional: a shared agent.conf to seed the group with.
SHARED_CONF="${SHARED_CONF:-}"

CURL=(curl -sS -k)   # TODO: drop -k and pass --cacert <root-ca.pem> in production.

log() { printf '[onboard %s] %s\n' "${TENANT}" "$*" >&2; }
die() { log "ERROR: $*"; exit 1; }

# ---------------------------------------------------------------------------
# 1. Authenticate. The manager API issues a JWT from basic credentials.
# ---------------------------------------------------------------------------
log "authenticating to ${WAZUH_API}"
TOKEN="$("${CURL[@]}" -u "${WAZUH_API_USER}:${WAZUH_API_PASS}" \
  -X POST "${WAZUH_API}/security/user/authenticate?raw=true")" \
  || die "authentication failed"
[ -n "${TOKEN}" ] || die "empty token returned"
AUTH=(-H "Authorization: Bearer ${TOKEN}")

# ---------------------------------------------------------------------------
# 2. Create the group. This has to happen before any agent enrols: Wazuh 5.0
#    rejects enrolment into a group that does not exist.
#    Idempotent-ish: a second run returns an error for an existing group,
#    which we tolerate rather than treat as fatal.
# ---------------------------------------------------------------------------
log "creating agent group ${GROUP}"
"${CURL[@]}" "${AUTH[@]}" -X POST "${WAZUH_API}/groups?pretty=true" \
  -H "Content-Type: application/json" \
  -d "{\"group_id\": \"${GROUP}\"}" || true

log "confirming the group exists"
"${CURL[@]}" "${AUTH[@]}" \
  "${WAZUH_API}/groups?pretty=true&groups_list=${GROUP}"

# ---------------------------------------------------------------------------
# 3. Seed the group's shared configuration.
#    The group's file lives on the manager at
#      /var/wazuh-manager/etc/shared/<GROUP>/agent.conf
#    and is distributed to every agent in the group. It is empty by default.
#
#    TODO: the manager API exposes PUT /groups/{group_id}/configuration for
#    this. Check the request shape in the API reference for your build before
#    enabling the block below; the alternative is to place the file on the
#    master node directly.
# ---------------------------------------------------------------------------
if [ -n "${SHARED_CONF}" ]; then
  [ -f "${SHARED_CONF}" ] || die "SHARED_CONF ${SHARED_CONF} not found"
  log "shared configuration supplied: ${SHARED_CONF}"
  log "TODO: upload it with PUT ${WAZUH_API}/groups/${GROUP}/configuration"
  log "      or copy it to /var/wazuh-manager/etc/shared/${GROUP}/agent.conf"
  log "      on the master node. Not uploaded automatically."
fi

# ---------------------------------------------------------------------------
# 4. Mint a rollout enrolment token, instead of handing out the shared
#    enrolment password from /var/wazuh-manager/etc/authd.pass.
#
#    The token text is returned in this response ONLY and is never listed
#    again. Tokens are minted on the master node.
#
#    A token does NOT bind an agent to a group: the group is declared
#    agent-side in <enrollment><groups>. Step 5 is the control.
# ---------------------------------------------------------------------------
if [ -n "${MANAGER_ADDRESS}" ]; then
  log "minting an enrolment token (ttl ${TOKEN_TTL}, max uses ${TOKEN_MAX_USES})"
  log "the token text is shown once and cannot be retrieved again"
  "${CURL[@]}" "${AUTH[@]}" \
    -X POST "${WAZUH_API}/agents/enrollment-tokens?pretty=true" \
    -H "Content-Type: application/json" \
    -d "{
          \"address\": \"${MANAGER_ADDRESS}\",
          \"ttl\": \"${TOKEN_TTL}\",
          \"max_uses\": ${TOKEN_MAX_USES},
          \"description\": \"Onboarding rollout for ${GROUP}\"
        }"
  # TODO: capture the token into your secret store here rather than leaving
  # it on stdout and in the operator's scrollback.
else
  log "MANAGER_ADDRESS not set; skipping enrolment token."
  log "It must match a name in the manager's HTTPS listener certificate."
fi

# ---------------------------------------------------------------------------
# 5. Reconcile. Run this again after every rollout, not just at onboarding.
#    Group membership drifts: rebuilt endpoints re-enrol, imaging templates
#    carry stale group names, and test machines wander.
# ---------------------------------------------------------------------------
log "agents currently in ${GROUP}"
"${CURL[@]}" "${AUTH[@]}" \
  "${WAZUH_API}/groups/${GROUP}/agents?pretty=true&select=id,name,ip,status"

log "agents with no group at all"
"${CURL[@]}" "${AUTH[@]}" \
  "${WAZUH_API}/agents/no_group?pretty=true&select=id,name,ip,status"

# TODO: diff the first list against the customer's asset inventory and alert
# on anything in the group that the inventory does not expect. An endpoint
# holding a valid token or the shared enrolment password can name ANY group
# that exists on the manager, including another tenant's.

cat >&2 <<'NEXT'

Manager-side onboarding done. Remaining steps, none of them automated here:

  1. Indexer security role       -> tenant-role.json
  2. Internal user or IdP group  -> tenant-rolesmapping.yml
  3. Wazuh role mapping          -> Server management > Security > Roles mapping
                                    (requires run_as: true in
                                     /etc/wazuh-dashboard/opensearch_dashboards.yml)
  4. Manager API policy + role   -> tenant-api-policy.json
  5. Dashboard tenant            -> Indexer management > Security > Tenants
  6. Notification channel        -> Explore > Notifications > Channels, with
                                    plugins.alerting.filter_by_backend_roles and
                                    opensearch.notifications.general.filter_by_backend_roles
                                    set to true
  7. Report definition           -> Explore > Reporting

NEXT
