#!/bin/bash # Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one # or more contributor license agreements. Licensed under the Elastic License 2.0; you may not use # this file except in compliance with the Elastic License 2.0. {%- import_yaml 'elasticfleet/defaults.yaml' as ELASTICFLEETDEFAULTS %} {% set SUB = salt['pillar.get']('elasticfleet:config:subscription_integrations', default=false) %} {% set AUTO_UPGRADE_INTEGRATIONS = salt['pillar.get']('elasticfleet:config:auto_upgrade_integrations', default=false) %} {%- set SUPPORTED_PACKAGES = salt['pillar.get']('elasticfleet:packages', default=ELASTICFLEETDEFAULTS.elasticfleet.packages, merge=True) %} . /usr/sbin/so-common . /usr/sbin/so-elastic-fleet-common # Check that /opt/so/state/estemplates.txt exists to signal that Elasticsearch # has completed its first run of core-only integrations/indices/components/ilm STATE_FILE_SUCCESS=/opt/so/state/estemplates.txt INSTALLED_PACKAGE_LIST=/tmp/esfleet_installed_packages.json BULK_INSTALL_PACKAGE_LIST=/tmp/esfleet_bulk_install.json BULK_INSTALL_PACKAGE_TMP=/tmp/esfleet_bulk_install_tmp.json BULK_INSTALL_OUTPUT=/opt/so/state/esfleet_bulk_install_results.json PACKAGE_COMPONENTS=/opt/so/state/esfleet_package_components.json COMPONENT_TEMPLATES=/opt/so/state/esfleet_component_templates.json PENDING_UPDATE=false # Integrations which are included in the package registry, but excluded from automatic installation via this script. # Requiring some level of manual Elastic Stack configuration before installation EXCLUDED_INTEGRATIONS=('apm') version_conversion(){ version=$1 echo "$version" | awk -F '.' '{ printf("%d%03d%03d\n", $1, $2, $3); }' } compare_versions() { version1=$1 version2=$2 # Convert versions to numbers num1=$(version_conversion "$version1") num2=$(version_conversion "$version2") # Compare using bc if (( $(echo "$num1 < $num2" | bc -l) )); then echo "less" elif (( $(echo "$num1 > $num2" | bc -l) )); then echo "greater" else echo "equal" fi } IFS=$'\n' agent_policies=$(elastic_fleet_agent_policy_ids) if [ $? -ne 0 ]; then echo "Error: Failed to retrieve agent policies." exit 1 fi default_packages=({% for pkg in SUPPORTED_PACKAGES %}"{{ pkg }}"{% if not loop.last %} {% endif %}{% endfor %}) in_use_integrations=() for AGENT_POLICY in $agent_policies; do if ! integrations=$(elastic_fleet_integration_policy_names "$AGENT_POLICY"); then # skip the agent policy if we can't get required info, let salt retry. Integrations loaded by this script are non-default integrations. echo "Skipping $AGENT_POLICY.. " continue fi for INTEGRATION in $integrations; do if ! PACKAGE_NAME=$(elastic_fleet_integration_policy_package_name "$AGENT_POLICY" "$INTEGRATION"); then echo "Not adding $INTEGRATION, couldn't get package name" continue fi # non-default integrations that are in-use in any policy if ! [[ " ${default_packages[@]} " =~ " $PACKAGE_NAME " ]]; then in_use_integrations+=("$PACKAGE_NAME") fi done done if [[ -f $STATE_FILE_SUCCESS ]]; then if retry 3 1 "curl -s -K /opt/so/conf/elasticsearch/curl.config --output /dev/null --silent --head --fail localhost:5601/api/fleet/epm/packages"; then # Package_list contains all integrations beta / non-beta. latest_package_list=$(/usr/sbin/so-elastic-fleet-package-list) echo '{ "packages" : []}' > $BULK_INSTALL_PACKAGE_LIST rm -f $INSTALLED_PACKAGE_LIST echo $latest_package_list | jq '{packages: [.items[] | {name: .name, latest_version: .version, installed_version: .savedObject.attributes.install_version, subscription: .conditions.elastic.subscription }]}' >> $INSTALLED_PACKAGE_LIST while read -r package; do # get package details package_name=$(echo "$package" | jq -r '.name') latest_version=$(echo "$package" | jq -r '.latest_version') installed_version=$(echo "$package" | jq -r '.installed_version') subscription=$(echo "$package" | jq -r '.subscription') bulk_package=$(echo "$package" | jq '{name: .name, version: .latest_version}' ) if [[ ! "${EXCLUDED_INTEGRATIONS[@]}" =~ "$package_name" ]]; then {% if not SUB %} if [[ "$subscription" != "basic" && "$subscription" != "null" && -n "$subscription" ]]; then # pass over integrations that require non-basic elastic license echo "$package_name integration requires an Elastic license of $subscription or greater... skipping" continue else if [[ "$installed_version" == "null" || -z "$installed_version" ]]; then echo "$package_name is not installed... Adding to next update." jq --argjson package "$bulk_package" '.packages += [$package]' $BULK_INSTALL_PACKAGE_LIST > $BULK_INSTALL_PACKAGE_TMP && mv $BULK_INSTALL_PACKAGE_TMP $BULK_INSTALL_PACKAGE_LIST PENDING_UPDATE=true else results=$(compare_versions "$latest_version" "$installed_version") if [ $results == "greater" ]; then {#- When auto_upgrade_integrations is false, skip upgrading in_use_integrations #} {%- if not AUTO_UPGRADE_INTEGRATIONS %} if ! [[ " ${in_use_integrations[@]} " =~ " $package_name " ]]; then {%- endif %} echo "$package_name is at version $installed_version latest version is $latest_version... Adding to next update." jq --argjson package "$bulk_package" '.packages += [$package]' $BULK_INSTALL_PACKAGE_LIST > $BULK_INSTALL_PACKAGE_TMP && mv $BULK_INSTALL_PACKAGE_TMP $BULK_INSTALL_PACKAGE_LIST PENDING_UPDATE=true {%- if not AUTO_UPGRADE_INTEGRATIONS %} else echo "skipping available upgrade for in use integration - $package_name." fi {%- endif %} fi fi fi {% else %} if [[ "$installed_version" == "null" || -z "$installed_version" ]]; then echo "$package_name is not installed... Adding to next update." jq --argjson package "$bulk_package" '.packages += [$package]' $BULK_INSTALL_PACKAGE_LIST > $BULK_INSTALL_PACKAGE_TMP && mv $BULK_INSTALL_PACKAGE_TMP $BULK_INSTALL_PACKAGE_LIST PENDING_UPDATE=true else results=$(compare_versions "$latest_version" "$installed_version") if [ $results == "greater" ]; then {#- When auto_upgrade_integrations is false, skip upgrading in_use_integrations #} {%- if not AUTO_UPGRADE_INTEGRATIONS %} if ! [[ " ${in_use_integrations[@]} " =~ " $package_name " ]]; then {%- endif %} echo "$package_name is at version $installed_version latest version is $latest_version... Adding to next update." jq --argjson package "$bulk_package" '.packages += [$package]' $BULK_INSTALL_PACKAGE_LIST > $BULK_INSTALL_PACKAGE_TMP && mv $BULK_INSTALL_PACKAGE_TMP $BULK_INSTALL_PACKAGE_LIST PENDING_UPDATE=true {%- if not AUTO_UPGRADE_INTEGRATIONS %} else echo "skipping available upgrade for in use integration - $package_name." fi {%- endif %} fi fi {% endif %} else echo "Skipping $package_name..." fi done <<< "$(jq -c '.packages[]' "$INSTALLED_PACKAGE_LIST")" if [ "$PENDING_UPDATE" = true ]; then # Run chunked install of packages echo "" > $BULK_INSTALL_OUTPUT pkg_group=1 pkg_filename="${BULK_INSTALL_PACKAGE_LIST%.json}" jq -c '.packages | _nwise(25)' $BULK_INSTALL_PACKAGE_LIST | while read -r line; do echo "$line" | jq '{ "packages": . }' > "${pkg_filename}_${pkg_group}.json" pkg_group=$((pkg_group + 1)) done for file in "${pkg_filename}_"*.json; do [ -e "$file" ] || continue if ! elastic_fleet_bulk_package_install $file >> $BULK_INSTALL_OUTPUT; then # integrations loaded my this script are non-essential and shouldn't cause exit, skip them for now next highstate run can retry echo "Failed to complete a chunk of bulk package installs -- $file " continue fi done # cleanup any temp files for chunked package install rm -f ${pkg_filename}_*.json $BULK_INSTALL_PACKAGE_LIST else echo "Elastic integrations don't appear to need installation/updating..." fi # Write out file for generating index/component/ilm templates if latest_installed_package_list=$(elastic_fleet_installed_packages); then echo $latest_installed_package_list | jq '[.items[] | {name: .name, es_index_patterns: .dataStreams}]' > $PACKAGE_COMPONENTS fi if retry 3 1 "so-elasticsearch-query / --fail --output /dev/null"; then # Refresh installed component template list latest_component_templates_list=$(so-elasticsearch-query _component_template | jq '.component_templates[] | .name' | jq -s '.') echo $latest_component_templates_list > $COMPONENT_TEMPLATES fi else # This is the installation of add-on integrations and upgrade of existing integrations. Exiting without error, next highstate will attempt to re-run. echo "Elastic Fleet does not appear to be responding... Exiting... " exit 0 fi else # This message will appear when an update to core integration is made and this script is run at the same time as # elasticsearch.enabled -> detects change to core index settings -> deletes estemplates.txt echo "Elasticsearch may not be fully configured yet or is currently updating core index settings." exit 0 fi