From db60c27da23ecb1c4f2d632fc0e578cb932e55c3 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:24:23 -0500 Subject: [PATCH 1/6] fail cleanly when endpoints-initial is missing or has multiple enrollment tokens --- .../tools/sbin/so-elastic-fleet-common | 61 ++++++++++++++++--- ...ic-fleet-integration-policy-elastic-defend | 5 +- .../so-elastic-fleet-integration-policy-load | 4 ++ .../so-elastic-agent-gen-installers | 18 ++++-- .../tools/sbin_jinja/so-elastic-fleet-setup | 23 +------ 5 files changed, 74 insertions(+), 37 deletions(-) diff --git a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common index 855a28510..2e5ad084c 100644 --- a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common +++ b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common @@ -30,6 +30,49 @@ fleet_api() { curl -sK /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/${QUERYPATH}" "$@" --retry 3 --retry-delay 10 --fail 2>/dev/null } +elastic_fleet_require_agent_policy() { + local AGENT_POLICY=$1 + local POLICY_JSON + + POLICY_JSON=$(fleet_api "agent_policies/$AGENT_POLICY") + if ! jq -e '.item.package_policies' <<<"$POLICY_JSON" >/dev/null 2>&1; then + echo "Error: Agent policy '$AGENT_POLICY' was not found or is not visible in the current Kibana space." >&2 + return 1 + fi + + echo "$POLICY_JSON" +} + +elastic_fleet_active_enrollment_token() { + local POLICY_ID=$1 + local RESP TOKEN_COUNT API_KEY + + if ! RESP=$(fleet_api "enrollment_api_keys?perPage=100" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then + echo "Error: Failed to retrieve enrollment tokens for agent policy '$POLICY_ID'." >&2 + return 1 + fi + + if ! jq -e '.list' <<<"$RESP" >/dev/null 2>&1; then + echo "Error: Invalid enrollment token response for agent policy '$POLICY_ID'." >&2 + return 1 + fi + + TOKEN_COUNT=$(jq --arg pid "$POLICY_ID" '[.list[] | select(.policy_id == $pid and .active == true)] | length' <<<"$RESP") + + if [ "$TOKEN_COUNT" -eq 0 ]; then + echo "Error: No active enrollment token found for agent policy '$POLICY_ID'." >&2 + return 1 + fi + + if [ "$TOKEN_COUNT" -gt 1 ]; then + echo "Error: Found $TOKEN_COUNT active enrollment tokens for agent policy '$POLICY_ID'; expected exactly one." >&2 + return 1 + fi + + API_KEY=$(jq -r --arg pid "$POLICY_ID" '.list[] | select(.policy_id == $pid and .active == true) | .api_key' <<<"$RESP") + echo "$API_KEY" +} + # Max number of concurrent Fleet write jobs (create/update). Override via env if needed. MAX_FLEET_JOBS=${MAX_FLEET_JOBS:-10} @@ -62,15 +105,7 @@ elastic_fleet_load_integrations_dir() { i=0 # Fetch the agent policy a single time; we look up integration ids locally below. - if ! POLICY_JSON=$(fleet_api "agent_policies/$AGENT_POLICY"); then - echo "Error: Failed to retrieve agent policy '$AGENT_POLICY'." - rm -f "$FAIL_FILE" - rm -rf "$OUT_DIR" - return 1 - fi - - if ! jq -e '.item.package_policies' <<<"$POLICY_JSON" >/dev/null 2>&1; then - echo "Error: Invalid agent policy response for '$AGENT_POLICY'." + if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$AGENT_POLICY"); then rm -f "$FAIL_FILE" rm -rf "$OUT_DIR" return 1 @@ -125,8 +160,14 @@ elastic_fleet_integration_check() { JSON_STRING=$2 NAME=$(jq -r .name $JSON_STRING) + INTEGRATION_ID="" - INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$AGENT_POLICY" | jq -r '.item.package_policies[] | select(.name=="'"$NAME"'") | .id') + local POLICY_JSON + if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$AGENT_POLICY"); then + return 1 + fi + + INTEGRATION_ID=$(jq -r --arg n "$NAME" '.item.package_policies[]? | select(.name==$n) | .id' <<<"$POLICY_JSON") } diff --git a/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-elastic-defend b/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-elastic-defend index d036f0d94..013a8089d 100755 --- a/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-elastic-defend +++ b/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-elastic-defend @@ -13,7 +13,10 @@ ERROR=false for INTEGRATION in /opt/so/conf/elastic-fleet/integrations/elastic-defend/*.json do printf "\n\nInitial Endpoints Policy - Loading $INTEGRATION\n" - elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION" + if ! elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION"; then + ERROR=true + continue + fi if [ -n "$INTEGRATION_ID" ]; then printf "\n\nIntegration $NAME exists - Upgrading integration policy\n" if ! elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"; then diff --git a/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-load b/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-load index 7c2aeb006..57569b56b 100644 --- a/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-load +++ b/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-load @@ -10,6 +10,10 @@ RETURN_CODE=0 if [ ! -f /opt/so/state/eaintegrations.txt ]; then + for AGENT_POLICY in endpoints-initial so-grid-nodes_general so-grid-nodes_heavy; do + elastic_fleet_require_agent_policy "$AGENT_POLICY" >/dev/null || exit 1 + done + # update Fleet Server policies /usr/sbin/so-elastic-fleet-integration-policy-elastic-fleet-server diff --git a/salt/elasticfleet/tools/sbin_jinja/so-elastic-agent-gen-installers b/salt/elasticfleet/tools/sbin_jinja/so-elastic-agent-gen-installers index de342657f..344f2bdc7 100755 --- a/salt/elasticfleet/tools/sbin_jinja/so-elastic-agent-gen-installers +++ b/salt/elasticfleet/tools/sbin_jinja/so-elastic-agent-gen-installers @@ -22,12 +22,12 @@ NUM_RUNNING=$(pgrep -cf "/bin/bash /sbin/so-elastic-agent-gen-installers") for i in {1..30} do - ENROLLMENTOKEN=$(curl -K /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/enrollment_api_keys?perPage=100" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' | jq .list | jq -r -c '.[] | select(.policy_id | contains("endpoints-initial")) | .api_key') + ENROLLMENTOKEN=$(elastic_fleet_active_enrollment_token "endpoints-initial") FLEETHOST=$(curl -K /opt/so/conf/elasticsearch/curl.config 'http://localhost:5601/api/fleet/fleet_server_hosts/grid-default' | jq -r '.item.host_urls[]' | paste -sd ',') if [[ $FLEETHOST ]] && [[ $ENROLLMENTOKEN ]]; then break; else sleep 10; fi done -if [[ -z $FLEETHOST ]] || [[ -z $ENROLLMENTOKEN ]]; then +if [[ -z "$FLEETHOST" ]] || [[ -z "$ENROLLMENTOKEN" ]]; then printf "\nFleet Host URL, Enrollment Token or Elastic Version empty - exiting..." printf "\nFleet Host: $FLEETHOST, Enrollment Token: $ENROLLMENTOKEN\n" exit 1 @@ -67,19 +67,25 @@ for GOOS in "${GOTARGETOS[@]}"; do GOARCH="amd64" if [[ $GOOS == 'darwin/arm64' ]]; then GOOS="darwin" && GOARCH="arm64"; fi printf "\n\n### Generating $GOOS/$GOARCH Installer...\n" - docker run -e CGO_ENABLED=0 -e GOOS=$GOOS -e GOARCH=$GOARCH \ + if ! docker run -e CGO_ENABLED=0 -e GOOS=$GOOS -e GOARCH=$GOARCH \ --mount type=bind,source=/etc/pki/tls/certs/,target=/workspace/files/cert/ \ --mount type=bind,source=/nsm/elastic-agent-workspace/,target=/workspace/files/elastic-agent/ \ --mount type=bind,source=/opt/so/saltstack/local/salt/elasticfleet/files/,target=/output/ \ - {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} go build -ldflags "-X main.fleetHostURLsList=$FLEETHOST -X main.enrollmentToken=$ENROLLMENTOKEN" -o /output/so-elastic-agent_${GOOS}_${GOARCH} + {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} go build -ldflags "-X main.fleetHostURLsList=$FLEETHOST -X main.enrollmentToken=$ENROLLMENTOKEN" -o /output/so-elastic-agent_${GOOS}_${GOARCH}; then + printf "\n### ERROR: Failed to generate $GOOS/$GOARCH installer. Exiting...\n" + exit 1 + fi printf "\n### $GOOS/$GOARCH Installer Generated...\n" done printf "\n\n### Generating MSI...\n" cp /opt/so/saltstack/local/salt/elasticfleet/files/so-elastic-agent_windows_amd64 /opt/so/saltstack/local/salt/elasticfleet/files/so-elastic-agent_windows_amd64.exe -docker run \ +if ! docker run \ --mount type=bind,source=/opt/so/saltstack/local/salt/elasticfleet/files/,target=/output/ -w /output \ -{{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} wixl -o so-elastic-agent_windows_amd64_msi --arch x64 /workspace/so-elastic-agent.wxs +{{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} wixl -o so-elastic-agent_windows_amd64_msi --arch x64 /workspace/so-elastic-agent.wxs; then + printf "\n### ERROR: Failed to generate MSI. Exiting...\n" + exit 1 +fi printf "\n### MSI Generated...\n" # Verify installers were created diff --git a/salt/elasticfleet/tools/sbin_jinja/so-elastic-fleet-setup b/salt/elasticfleet/tools/sbin_jinja/so-elastic-fleet-setup index 22e0c7554..77c45c16e 100755 --- a/salt/elasticfleet/tools/sbin_jinja/so-elastic-fleet-setup +++ b/salt/elasticfleet/tools/sbin_jinja/so-elastic-fleet-setup @@ -202,26 +202,9 @@ fi ### Finalization ### # Query for Enrollment Tokens for default policies -if ENDPOINTSENROLLMENTOKEN_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then - ENDPOINTSENROLLMENTOKEN=$(echo "$ENDPOINTSENROLLMENTOKEN_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("endpoints-initial")) | .api_key') -else - echo -e "\nFailed to query for Endpoints enrollment token" - exit 1 -fi - -if GRIDNODESENROLLMENTOKENGENERAL_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then - GRIDNODESENROLLMENTOKENGENERAL=$(echo "$GRIDNODESENROLLMENTOKENGENERAL_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("so-grid-nodes_general")) | .api_key') -else - echo -e "\nFailed to query for Grid nodes - General enrollment token" - exit 1 -fi - -if GRIDNODESENROLLMENTOKENHEAVY_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then - GRIDNODESENROLLMENTOKENHEAVY=$(echo "$GRIDNODESENROLLMENTOKENHEAVY_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("so-grid-nodes_heavy")) | .api_key') -else - echo -e "\nFailed to query for Grid nodes - Heavy enrollment token" - exit 1 -fi +ENDPOINTSENROLLMENTOKEN=$(elastic_fleet_active_enrollment_token "endpoints-initial") || exit 1 +GRIDNODESENROLLMENTOKENGENERAL=$(elastic_fleet_active_enrollment_token "so-grid-nodes_general") || exit 1 +GRIDNODESENROLLMENTOKENHEAVY=$(elastic_fleet_active_enrollment_token "so-grid-nodes_heavy") || exit 1 # Store needed data in minion pillar pillar_file=/opt/so/saltstack/local/pillar/minions/{{ GLOBALS.minion_id }}.sls From ebab4b0d903df52703ebe82b69996b22bba3fb49 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:11:56 -0500 Subject: [PATCH 2/6] split between missing token and multiple enrollment tokens --- .../tools/sbin/so-elastic-fleet-common | 16 ++++++++++-- ...et-integration-policy-elastic-fleet-server | 25 +++++++++++++++---- .../so-elastic-agent-gen-installers | 9 ++++++- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common index 2e5ad084c..9f6c25728 100644 --- a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common +++ b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common @@ -43,6 +43,9 @@ elastic_fleet_require_agent_policy() { echo "$POLICY_JSON" } +# Print the single active enrollment token for POLICY_ID. +# Exit 1: retryable (API failure, invalid response, no active token) +# Exit 2: multiple active tokens - Shouldn't get into this state without manual intervention elastic_fleet_active_enrollment_token() { local POLICY_ID=$1 local RESP TOKEN_COUNT API_KEY @@ -66,7 +69,7 @@ elastic_fleet_active_enrollment_token() { if [ "$TOKEN_COUNT" -gt 1 ]; then echo "Error: Found $TOKEN_COUNT active enrollment tokens for agent policy '$POLICY_ID'; expected exactly one." >&2 - return 1 + return 2 fi API_KEY=$(jq -r --arg pid "$POLICY_ID" '.list[] | select(.policy_id == $pid and .active == true) | .api_key' <<<"$RESP") @@ -189,7 +192,16 @@ elastic_fleet_integration_remove() { NAME=$2 - INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$AGENT_POLICY" | jq -r '.item.package_policies[] | select(.name=="'"$NAME"'") | .id') + local POLICY_JSON + if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$AGENT_POLICY"); then + return 1 + fi + + INTEGRATION_ID=$(jq -r --arg n "$NAME" '.item.package_policies[]? | select(.name==$n) | .id' <<<"$POLICY_JSON") + if [ -z "$INTEGRATION_ID" ]; then + echo "Error: Integration '$NAME' was not found in agent policy '$AGENT_POLICY'." >&2 + return 1 + fi JSON_STRING=$( jq -n \ --arg INTEGRATIONID "$INTEGRATION_ID" \ diff --git a/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-elastic-fleet-server b/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-elastic-fleet-server index caa684829..1a384b2aa 100644 --- a/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-elastic-fleet-server +++ b/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-elastic-fleet-server @@ -7,20 +7,35 @@ . /usr/sbin/so-elastic-fleet-common # Get all the fleet policies -json_output=$(curl -s -K /opt/so/conf/elasticsearch/curl.config -L -X GET "localhost:5601/api/fleet/agent_policies" -H 'kbn-xsrf: true') +if ! json_output=$(fleet_api "agent_policies" -H 'kbn-xsrf: true'); then + echo "Error: Failed to retrieve Fleet agent policies." >&2 + exit 1 +fi + +if ! jq -e '.items' <<<"$json_output" >/dev/null 2>&1; then + echo "Error: Invalid Fleet agent policies response." >&2 + exit 1 +fi # Extract the IDs that start with "FleetServer_" -POLICY=$(echo "$json_output" | jq -r '.items[] | select(.id | startswith("FleetServer_")) | .id') +POLICY=$(jq -r '.items[] | select(.id | startswith("FleetServer_")) | .id' <<<"$json_output") # Iterate over each ID in the POLICY variable for POLICYNAME in $POLICY; do printf "\nUpdating Policy: $POLICYNAME\n" - # First get the Integration ID - INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$POLICYNAME" | jq -r '.item.package_policies[] | select(.package.name == "fleet_server") | .id') + if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$POLICYNAME"); then + exit 1 + fi + + INTEGRATION_ID=$(jq -r '.item.package_policies[]? | select(.package.name == "fleet_server") | .id' <<<"$POLICY_JSON") + if [ -z "$INTEGRATION_ID" ]; then + echo "Error: fleet_server integration was not found in agent policy '$POLICYNAME'." >&2 + exit 1 + fi # Modify the default integration policy to update the policy_id and an with the correct naming - UPDATED_INTEGRATION_POLICY=$(jq --arg policy_id "$POLICYNAME" --arg name "fleet_server-$POLICYNAME" ' + UPDATED_INTEGRATION_POLICY=$(jq --arg policy_id "$POLICYNAME" --arg name "fleet_server-$POLICYNAME" ' .policy_id = $policy_id | .name = $name' /opt/so/conf/elastic-fleet/integrations/fleet-server/fleet-server.json) diff --git a/salt/elasticfleet/tools/sbin_jinja/so-elastic-agent-gen-installers b/salt/elasticfleet/tools/sbin_jinja/so-elastic-agent-gen-installers index 344f2bdc7..c457c6dc6 100755 --- a/salt/elasticfleet/tools/sbin_jinja/so-elastic-agent-gen-installers +++ b/salt/elasticfleet/tools/sbin_jinja/so-elastic-agent-gen-installers @@ -23,8 +23,15 @@ NUM_RUNNING=$(pgrep -cf "/bin/bash /sbin/so-elastic-agent-gen-installers") for i in {1..30} do ENROLLMENTOKEN=$(elastic_fleet_active_enrollment_token "endpoints-initial") + TOKEN_RC=$? + if [ "$TOKEN_RC" -eq 2 ]; then + exit 1 + fi FLEETHOST=$(curl -K /opt/so/conf/elasticsearch/curl.config 'http://localhost:5601/api/fleet/fleet_server_hosts/grid-default' | jq -r '.item.host_urls[]' | paste -sd ',') -if [[ $FLEETHOST ]] && [[ $ENROLLMENTOKEN ]]; then break; else sleep 10; fi + if [[ -n "$FLEETHOST" ]] && [[ -n "$ENROLLMENTOKEN" ]]; then + break + fi + sleep 10 done if [[ -z "$FLEETHOST" ]] || [[ -z "$ENROLLMENTOKEN" ]]; then From 6ce89eb3234b288b41c80ccd8df5767cac8262be Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Tue, 22 Sep 2026 21:25:50 -0500 Subject: [PATCH 3/6] jq -e exits 0 with empty input --- salt/elasticfleet/tools/sbin/so-elastic-fleet-common | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common index 9f6c25728..3a35c4c0c 100644 --- a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common +++ b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common @@ -34,8 +34,12 @@ elastic_fleet_require_agent_policy() { local AGENT_POLICY=$1 local POLICY_JSON - POLICY_JSON=$(fleet_api "agent_policies/$AGENT_POLICY") - if ! jq -e '.item.package_policies' <<<"$POLICY_JSON" >/dev/null 2>&1; then + if ! POLICY_JSON=$(fleet_api "agent_policies/$AGENT_POLICY") || [ -z "$POLICY_JSON" ]; then + echo "Error: Agent policy '$AGENT_POLICY' was not found or is not visible in the current Kibana space." >&2 + return 1 + fi + + if ! jq -e '.item.package_policies | type == "array"' <<<"$POLICY_JSON" >/dev/null 2>&1; then echo "Error: Agent policy '$AGENT_POLICY' was not found or is not visible in the current Kibana space." >&2 return 1 fi From bcee63bde5e73543e88c0a838f8294d93fcd524b Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Wed, 23 Sep 2026 08:51:11 -0500 Subject: [PATCH 4/6] remove policy precheck --- salt/elasticfleet/tools/sbin/so-elastic-fleet-common | 4 ++-- .../tools/sbin/so-elastic-fleet-integration-policy-load | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common index 3a35c4c0c..777e79921 100644 --- a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common +++ b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common @@ -66,7 +66,7 @@ elastic_fleet_active_enrollment_token() { TOKEN_COUNT=$(jq --arg pid "$POLICY_ID" '[.list[] | select(.policy_id == $pid and .active == true)] | length' <<<"$RESP") - if [ "$TOKEN_COUNT" -eq 0 ]; then + if [ "${TOKEN_COUNT:-0}" -eq 0 ]; then echo "Error: No active enrollment token found for agent policy '$POLICY_ID'." >&2 return 1 fi @@ -166,7 +166,7 @@ elastic_fleet_integration_check() { JSON_STRING=$2 - NAME=$(jq -r .name $JSON_STRING) + NAME=$(jq -r .name "$JSON_STRING") INTEGRATION_ID="" local POLICY_JSON diff --git a/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-load b/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-load index 57569b56b..7c2aeb006 100644 --- a/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-load +++ b/salt/elasticfleet/tools/sbin/so-elastic-fleet-integration-policy-load @@ -10,10 +10,6 @@ RETURN_CODE=0 if [ ! -f /opt/so/state/eaintegrations.txt ]; then - for AGENT_POLICY in endpoints-initial so-grid-nodes_general so-grid-nodes_heavy; do - elastic_fleet_require_agent_policy "$AGENT_POLICY" >/dev/null || exit 1 - done - # update Fleet Server policies /usr/sbin/so-elastic-fleet-integration-policy-elastic-fleet-server From efe0581892d3737822185b88cb816e181faa3c56 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Thu, 24 Sep 2026 15:27:51 -0400 Subject: [PATCH 5/6] Add openai_embeddings to the YAML configuration --- salt/soc/soc_soc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/salt/soc/soc_soc.yaml b/salt/soc/soc_soc.yaml index 3d1594437..365775259 100644 --- a/salt/soc/soc_soc.yaml +++ b/salt/soc/soc_soc.yaml @@ -791,6 +791,7 @@ soc: - gemini - openai_responses - openai_chat + - openai_embeddings - field: apiUrl label: API URL required: False From 21222ff119035d503987069d39dd4d567b81e6b3 Mon Sep 17 00:00:00 2001 From: Josh Patterson Date: Fri, 25 Sep 2026 09:23:09 -0400 Subject: [PATCH 6/6] FIX: use /sbin/nologin for service accounts These accounts existed only for container UID mapping and filesystem ownership, but user.present omitted shell:, so Salt fell through to the platform useradd default and every one of them got /bin/bash. Pin them to /sbin/nologin so none can be used as an interactive login or `su -` target. socore keeps /bin/bash: `su socore -c '/usr/sbin/so-repo-sync'` in soup and so-kernel-upgrade execs the account's passwd shell, and operator docs tell users to su to socore. soqemussh keeps /bin/bash as an SSH login account. elastic-agent, elastic-agent-pr and kafka are included alongside the accounts named in the issue, being the same class with the same unset shell, so the default is uniform. Cron is unaffected: cronie runs jobs via the crontab SHELL (default /bin/sh), not the passwd shell. suricata is the only account changed here that owns a crontab, and somon has shipped as nologin with a working cron job already. The zeek `runuser -l zeek` calls all run inside so-zeek via docker.run/exec, so they resolve the shell from the image, not the host. Verified on a 3.4.0 managersearch + sensor grid: highstate converges with the shell as the only change and no failures, is idempotent on a second run, all containers stay up, SOC still issues a Kratos login flow, and the suricata surilogcompress cron job runs post-change ((suricata) CMD/CMDEND in /var/log/cron) while `su - suricata` is now refused. Closes #16256 --- salt/elastalert/config.sls | 1 + salt/elastic-fleet-package-registry/config.sls | 1 + salt/elasticagent/config.sls | 1 + salt/elasticfleet/config.sls | 1 + salt/elasticsearch/config.sls | 1 + salt/kafka/config.sls | 1 + salt/kibana/config.sls | 1 + salt/kratos/config.sls | 1 + salt/logstash/config.sls | 1 + salt/suricata/config.sls | 1 + salt/zeek/config.sls | 1 + 11 files changed, 11 insertions(+) diff --git a/salt/elastalert/config.sls b/salt/elastalert/config.sls index 147666e6e..f75cbc1a4 100644 --- a/salt/elastalert/config.sls +++ b/salt/elastalert/config.sls @@ -21,6 +21,7 @@ elastalert: - gid: 933 - home: /opt/so/conf/elastalert - createhome: False + - shell: /sbin/nologin elastalogdir: file.directory: diff --git a/salt/elastic-fleet-package-registry/config.sls b/salt/elastic-fleet-package-registry/config.sls index aa2872069..f49d9ba7b 100644 --- a/salt/elastic-fleet-package-registry/config.sls +++ b/salt/elastic-fleet-package-registry/config.sls @@ -19,6 +19,7 @@ elastic-agent-pr: - gid: 948 - home: /opt/so/conf/elastic-fleet-pr - createhome: False + - shell: /sbin/nologin {% else %} diff --git a/salt/elasticagent/config.sls b/salt/elasticagent/config.sls index 63992b199..f572515eb 100644 --- a/salt/elasticagent/config.sls +++ b/salt/elasticagent/config.sls @@ -20,6 +20,7 @@ elastic-agent: - gid: 949 - home: /opt/so/conf/elastic-agent - createhome: False + - shell: /sbin/nologin elasticagentconfdir: file.directory: diff --git a/salt/elasticfleet/config.sls b/salt/elasticfleet/config.sls index 59f052b7f..d5dc3595d 100644 --- a/salt/elasticfleet/config.sls +++ b/salt/elasticfleet/config.sls @@ -26,6 +26,7 @@ elastic-fleet: - gid: 947 - home: /opt/so/conf/elastic-fleet - createhome: False + - shell: /sbin/nologin elasticfleet_sbin: file.recurse: diff --git a/salt/elasticsearch/config.sls b/salt/elasticsearch/config.sls index 5c1645ca6..d7a5c0439 100644 --- a/salt/elasticsearch/config.sls +++ b/salt/elasticsearch/config.sls @@ -32,6 +32,7 @@ elasticsearch: - gid: 930 - home: /opt/so/conf/elasticsearch - createhome: False + - shell: /sbin/nologin elasticsearch_sbin: file.recurse: diff --git a/salt/kafka/config.sls b/salt/kafka/config.sls index 6a7c30c94..10caf8ec5 100644 --- a/salt/kafka/config.sls +++ b/salt/kafka/config.sls @@ -21,6 +21,7 @@ kafka_user: - gid: 960 - home: /opt/so/conf/kafka - createhome: False + - shell: /sbin/nologin kafka_home_dir: file.absent: diff --git a/salt/kibana/config.sls b/salt/kibana/config.sls index bc4e5f431..4638f8b75 100644 --- a/salt/kibana/config.sls +++ b/salt/kibana/config.sls @@ -22,6 +22,7 @@ kibana: - gid: 932 - home: /opt/so/conf/kibana - createhome: False + - shell: /sbin/nologin # Drop the correct nginx config based on role diff --git a/salt/kratos/config.sls b/salt/kratos/config.sls index 622522e0b..d9a963920 100644 --- a/salt/kratos/config.sls +++ b/salt/kratos/config.sls @@ -27,6 +27,7 @@ kratos: - uid: 928 - gid: 928 - home: /opt/so/conf/kratos + - shell: /sbin/nologin kratosdir: file.directory: diff --git a/salt/logstash/config.sls b/salt/logstash/config.sls index 7a09349fc..bbd5bf041 100644 --- a/salt/logstash/config.sls +++ b/salt/logstash/config.sls @@ -35,6 +35,7 @@ logstash: - uid: 931 - gid: 931 - home: /opt/so/conf/logstash + - shell: /sbin/nologin logstash_sbin: file.recurse: diff --git a/salt/suricata/config.sls b/salt/suricata/config.sls index dd228ef31..9b78f8907 100644 --- a/salt/suricata/config.sls +++ b/salt/suricata/config.sls @@ -64,6 +64,7 @@ suricata: - gid: 940 - home: /nsm/suricata - createhome: False + - shell: /sbin/nologin socoregroupwithsuricata: group.present: diff --git a/salt/zeek/config.sls b/salt/zeek/config.sls index 17a495010..ccf51a3f6 100644 --- a/salt/zeek/config.sls +++ b/salt/zeek/config.sls @@ -23,6 +23,7 @@ zeek: - gid: 937 - home: /opt/so/conf/zeek - createhome: False + - shell: /sbin/nologin # Create some directories zeekpolicydir: