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/elasticfleet/tools/sbin/so-elastic-fleet-common b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common index 855a28510..777e79921 100644 --- a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common +++ b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common @@ -30,6 +30,56 @@ 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 + + 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 + + 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 + + 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:-0}" -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 2 + 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 +112,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 @@ -124,9 +166,15 @@ elastic_fleet_integration_check() { JSON_STRING=$2 - NAME=$(jq -r .name $JSON_STRING) + 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") } @@ -148,7 +196,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-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-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 de342657f..c457c6dc6 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,19 @@ 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") + 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 +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 +74,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 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/soc/soc_soc.yaml b/salt/soc/soc_soc.yaml index 641ac4d58..adb19833e 100644 --- a/salt/soc/soc_soc.yaml +++ b/salt/soc/soc_soc.yaml @@ -839,6 +839,7 @@ soc: - gemini - openai_responses - openai_chat + - openai_embeddings - field: apiUrl label: API URL required: False 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: