From c86399327bbab0f34ec05f9a1fda26725ee3142e Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Tue, 28 Apr 2026 14:27:59 -0400 Subject: [PATCH 01/13] fix so-docker-refresh push for multi-arch source images docker pull of a multi-arch tag on Docker 29.x leaves the local tag pointing at the image index rather than the platform-specific manifest. The subsequent docker push then tries to push every sub-manifest the index references and fails on layers we never fetched. Resolve the local-platform manifest digest from the upstream index via docker buildx imagetools inspect, pull by that digest, and re-tag locally to the canonical tag. The signing flow and the existing tag/push to the embedded registry are unchanged. --- salt/common/tools/sbin/so-image-common | 44 +++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/salt/common/tools/sbin/so-image-common b/salt/common/tools/sbin/so-image-common index e8f604681..b91bb07e1 100755 --- a/salt/common/tools/sbin/so-image-common +++ b/salt/common/tools/sbin/so-image-common @@ -104,6 +104,16 @@ update_docker_containers() { set_version set_os + # Resolve the local Docker daemon's platform once. Used below to pick the + # matching sub-manifest from a multi-arch image index — Docker 29.x will not + # push a tag that points at an index unless every referenced sub-manifest's + # content is present locally. + local PLATFORM_OS PLATFORM_ARCH + PLATFORM_OS=$(docker version --format '{{.Server.Os}}' 2>/dev/null) + PLATFORM_ARCH=$(docker version --format '{{.Server.Arch}}' 2>/dev/null) + PLATFORM_OS=${PLATFORM_OS:-linux} + PLATFORM_ARCH=${PLATFORM_ARCH:-amd64} + if [ -z "$TRUSTED_CONTAINERS" ]; then container_list fi @@ -161,10 +171,36 @@ update_docker_containers() { local image=$i:$VERSION$IMAGE_TAG_SUFFIX local sig_url=https://sigs.securityonion.net/$VERSION/$image.sig fi - # Pull down the trusted docker image - run_check_net_err \ - "docker pull $CONTAINER_REGISTRY/$IMAGEREPO/$image" \ - "Could not pull $image, please ensure connectivity to $CONTAINER_REGISTRY" >> "$LOG_FILE" 2>&1 + # Pull down the trusted docker image. If the upstream tag is a multi-arch + # image index, resolve the local-platform manifest digest and pull by + # digest so the local tag points at a single-arch image (not an index). + # That keeps Docker 29.x's index-aware push from trying to push sub-manifests + # whose content we never fetched. + local FULL_IMAGE="$CONTAINER_REGISTRY/$IMAGEREPO/$image" + local PLATFORM_DIGEST + PLATFORM_DIGEST=$(docker buildx imagetools inspect --raw "$FULL_IMAGE" 2>/dev/null \ + | jq -r --arg os "$PLATFORM_OS" --arg arch "$PLATFORM_ARCH" ' + .manifests[]? + | select(.platform.os == $os + and .platform.architecture == $arch + and ((.platform.variant // "") == "") + and ((.annotations["vnd.docker.reference.type"] // "") != "attestation-manifest")) + | .digest' 2>/dev/null \ + | head -n 1) + + if [ -n "$PLATFORM_DIGEST" ] && [ "$PLATFORM_DIGEST" != "null" ]; then + run_check_net_err \ + "docker pull $FULL_IMAGE@$PLATFORM_DIGEST" \ + "Could not pull $image ($PLATFORM_OS/$PLATFORM_ARCH), please ensure connectivity to $CONTAINER_REGISTRY" >> "$LOG_FILE" 2>&1 + docker tag "$FULL_IMAGE@$PLATFORM_DIGEST" "$FULL_IMAGE" >> "$LOG_FILE" 2>&1 || { + echo "Unable to retag $image to canonical tag" >> "$LOG_FILE" 2>&1 + exit 1 + } + else + run_check_net_err \ + "docker pull $FULL_IMAGE" \ + "Could not pull $image, please ensure connectivity to $CONTAINER_REGISTRY" >> "$LOG_FILE" 2>&1 + fi # Get signature run_check_net_err \ From 9cec79b2992b0ae6f402287abddf9ca314258a85 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:34:39 -0500 Subject: [PATCH 02/13] check current fleet policy cert against cert on disk Co-authored-by: Copilot --- .../tools/sbin_jinja/so-elastic-fleet-outputs-update | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/salt/elasticfleet/tools/sbin_jinja/so-elastic-fleet-outputs-update b/salt/elasticfleet/tools/sbin_jinja/so-elastic-fleet-outputs-update index f045bf753..8630799d8 100644 --- a/salt/elasticfleet/tools/sbin_jinja/so-elastic-fleet-outputs-update +++ b/salt/elasticfleet/tools/sbin_jinja/so-elastic-fleet-outputs-update @@ -235,6 +235,16 @@ function update_kafka_outputs() { {% endif %} +# Compare the current Elastic Fleet certificate against what is on disk +POLICY_CERT_SHA=$(jq -r '.item.ssl.certificate' <<< $RAW_JSON | openssl x509 -noout -sha256 -fingerprint) +DISK_CERT_SHA=$(openssl x509 -in /etc/pki/elasticfleet-logstash.crt -noout -sha256 -fingerprint) + +if [[ "$POLICY_CERT_SHA" != "$DISK_CERT_SHA" ]]; then + printf "Certificate on disk doesn't match certificate in policy - forcing update\n" + UPDATE_CERTS=true + FORCE_UPDATE=true +fi + # Sort & hash the new list of Logstash Outputs NEW_LIST_JSON=$(jq --compact-output --null-input '$ARGS.positional' --args -- "${NEW_LIST[@]}") NEW_HASH=$(sha256sum <<< "$NEW_LIST_JSON" | awk '{print $1}') From 288a823edf0776911c638c3fbf990762dfba9fd7 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Tue, 28 Apr 2026 14:49:02 -0400 Subject: [PATCH 03/13] push images via buildx imagetools create Replaces `docker push` with a registry-to-registry copy. On Docker 29.x with the containerd image store, `docker push` of a freshly-pulled image hits a path that wraps single-platform manifests in a synthetic index and then can't push the layers it claims to reference, producing `NotFound: content digest ...` even when the image is fully present. Keep the local `docker tag` so so-image-pull's `docker images | grep :5000` existence check continues to work. --- salt/common/tools/sbin/so-image-common | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/salt/common/tools/sbin/so-image-common b/salt/common/tools/sbin/so-image-common index b91bb07e1..049bb03f9 100755 --- a/salt/common/tools/sbin/so-image-common +++ b/salt/common/tools/sbin/so-image-common @@ -225,11 +225,20 @@ update_docker_containers() { HOSTNAME=$(hostname) fi docker tag $CONTAINER_REGISTRY/$IMAGEREPO/$image $HOSTNAME:5000/$IMAGEREPO/$image >> "$LOG_FILE" 2>&1 || { - echo "Unable to tag $image" >> "$LOG_FILE" 2>&1 + echo "Unable to tag $image" >> "$LOG_FILE" 2>&1 exit 1 } - docker push $HOSTNAME:5000/$IMAGEREPO/$image >> "$LOG_FILE" 2>&1 || { - echo "Unable to push $image" >> "$LOG_FILE" 2>&1 + # Push to the embedded registry via a registry-to-registry copy. Avoids + # `docker push`, which on Docker 29.x with the containerd image store + # represents freshly-pulled images as an index whose layer content + # isn't reachable through the push path. The local `docker tag` above + # is preserved so so-image-pull's `:5000` existence check still works. + local PUSH_SRC="$CONTAINER_REGISTRY/$IMAGEREPO/$image" + if [ -n "$PLATFORM_DIGEST" ] && [ "$PLATFORM_DIGEST" != "null" ]; then + PUSH_SRC="$CONTAINER_REGISTRY/$IMAGEREPO/$image@$PLATFORM_DIGEST" + fi + docker buildx imagetools create --tag $HOSTNAME:5000/$IMAGEREPO/$image "$PUSH_SRC" >> "$LOG_FILE" 2>&1 || { + echo "Unable to copy $image to embedded registry" >> "$LOG_FILE" 2>&1 exit 1 } fi From 82dac82d155e80afd1445903eee314a0a5afe718 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Tue, 28 Apr 2026 14:54:25 -0400 Subject: [PATCH 04/13] drop platform/digest pull resolution The digest-pull logic was added to make `docker push` work for multi-arch upstream tags. Now that the push step is `docker buildx imagetools create` pinned to the gpg-verified RepoDigest, the registry-to-registry copy handles single- and multi-arch sources without help. Reverts the pull back to the original line and removes the unused PLATFORM_OS/_ARCH detection. --- salt/common/tools/sbin/so-image-common | 56 ++++++-------------------- 1 file changed, 12 insertions(+), 44 deletions(-) diff --git a/salt/common/tools/sbin/so-image-common b/salt/common/tools/sbin/so-image-common index 049bb03f9..833b9a7d8 100755 --- a/salt/common/tools/sbin/so-image-common +++ b/salt/common/tools/sbin/so-image-common @@ -104,16 +104,6 @@ update_docker_containers() { set_version set_os - # Resolve the local Docker daemon's platform once. Used below to pick the - # matching sub-manifest from a multi-arch image index — Docker 29.x will not - # push a tag that points at an index unless every referenced sub-manifest's - # content is present locally. - local PLATFORM_OS PLATFORM_ARCH - PLATFORM_OS=$(docker version --format '{{.Server.Os}}' 2>/dev/null) - PLATFORM_ARCH=$(docker version --format '{{.Server.Arch}}' 2>/dev/null) - PLATFORM_OS=${PLATFORM_OS:-linux} - PLATFORM_ARCH=${PLATFORM_ARCH:-amd64} - if [ -z "$TRUSTED_CONTAINERS" ]; then container_list fi @@ -171,37 +161,11 @@ update_docker_containers() { local image=$i:$VERSION$IMAGE_TAG_SUFFIX local sig_url=https://sigs.securityonion.net/$VERSION/$image.sig fi - # Pull down the trusted docker image. If the upstream tag is a multi-arch - # image index, resolve the local-platform manifest digest and pull by - # digest so the local tag points at a single-arch image (not an index). - # That keeps Docker 29.x's index-aware push from trying to push sub-manifests - # whose content we never fetched. - local FULL_IMAGE="$CONTAINER_REGISTRY/$IMAGEREPO/$image" - local PLATFORM_DIGEST - PLATFORM_DIGEST=$(docker buildx imagetools inspect --raw "$FULL_IMAGE" 2>/dev/null \ - | jq -r --arg os "$PLATFORM_OS" --arg arch "$PLATFORM_ARCH" ' - .manifests[]? - | select(.platform.os == $os - and .platform.architecture == $arch - and ((.platform.variant // "") == "") - and ((.annotations["vnd.docker.reference.type"] // "") != "attestation-manifest")) - | .digest' 2>/dev/null \ - | head -n 1) + # Pull down the trusted docker image + run_check_net_err \ + "docker pull $CONTAINER_REGISTRY/$IMAGEREPO/$image" \ + "Could not pull $image, please ensure connectivity to $CONTAINER_REGISTRY" >> "$LOG_FILE" 2>&1 - if [ -n "$PLATFORM_DIGEST" ] && [ "$PLATFORM_DIGEST" != "null" ]; then - run_check_net_err \ - "docker pull $FULL_IMAGE@$PLATFORM_DIGEST" \ - "Could not pull $image ($PLATFORM_OS/$PLATFORM_ARCH), please ensure connectivity to $CONTAINER_REGISTRY" >> "$LOG_FILE" 2>&1 - docker tag "$FULL_IMAGE@$PLATFORM_DIGEST" "$FULL_IMAGE" >> "$LOG_FILE" 2>&1 || { - echo "Unable to retag $image to canonical tag" >> "$LOG_FILE" 2>&1 - exit 1 - } - else - run_check_net_err \ - "docker pull $FULL_IMAGE" \ - "Could not pull $image, please ensure connectivity to $CONTAINER_REGISTRY" >> "$LOG_FILE" 2>&1 - fi - # Get signature run_check_net_err \ "curl --retry 5 --retry-delay 60 -A '$CURLTYPE/$CURRENTVERSION/$OS/$(uname -r)' $sig_url --output $SIGNPATH/$image.sig" \ @@ -233,11 +197,15 @@ update_docker_containers() { # represents freshly-pulled images as an index whose layer content # isn't reachable through the push path. The local `docker tag` above # is preserved so so-image-pull's `:5000` existence check still works. - local PUSH_SRC="$CONTAINER_REGISTRY/$IMAGEREPO/$image" - if [ -n "$PLATFORM_DIGEST" ] && [ "$PLATFORM_DIGEST" != "null" ]; then - PUSH_SRC="$CONTAINER_REGISTRY/$IMAGEREPO/$image@$PLATFORM_DIGEST" + # Pin to the digest already gpg-verified above so we copy exactly the + # bytes we approved. + local VERIFIED_REF + VERIFIED_REF=$(echo "$DOCKERINSPECT" | jq -r ".[0].RepoDigests[] | select(. | contains(\"$CONTAINER_REGISTRY\"))" | head -n 1) + if [ -z "$VERIFIED_REF" ] || [ "$VERIFIED_REF" = "null" ]; then + echo "Unable to determine verified digest for $image" >> "$LOG_FILE" 2>&1 + exit 1 fi - docker buildx imagetools create --tag $HOSTNAME:5000/$IMAGEREPO/$image "$PUSH_SRC" >> "$LOG_FILE" 2>&1 || { + docker buildx imagetools create --tag $HOSTNAME:5000/$IMAGEREPO/$image "$VERIFIED_REF" >> "$LOG_FILE" 2>&1 || { echo "Unable to copy $image to embedded registry" >> "$LOG_FILE" 2>&1 exit 1 } From 2dcded6ccad1c0ffae81f791b5aadabf8b1061f4 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Tue, 28 Apr 2026 15:46:56 -0400 Subject: [PATCH 05/13] drop postgres module from soc defaults injection The soc binary on 3/dev does not register a postgres module, so injecting postgres into soc.config.server.modules makes soc abort at launch with 'Module does not exist: postgres'. The soc-side module is staged on feature/postgres but is not landing this release. Drop the injection until the module ships; salt/postgres state and pillars are unchanged. --- salt/soc/defaults.map.jinja | 5 ----- 1 file changed, 5 deletions(-) diff --git a/salt/soc/defaults.map.jinja b/salt/soc/defaults.map.jinja index 00a9604f6..2821bb8e5 100644 --- a/salt/soc/defaults.map.jinja +++ b/salt/soc/defaults.map.jinja @@ -24,11 +24,6 @@ {% do SOCDEFAULTS.soc.config.server.modules.elastic.update({'username': GLOBALS.elasticsearch.auth.users.so_elastic_user.user, 'password': GLOBALS.elasticsearch.auth.users.so_elastic_user.pass}) %} -{% if GLOBALS.postgres is defined and GLOBALS.postgres.auth is defined %} -{% set PG_ADMIN_PASS = salt['pillar.get']('secrets:postgres_pass', '') %} -{% do SOCDEFAULTS.soc.config.server.modules.update({'postgres': {'hostUrl': GLOBALS.manager_ip, 'port': 5432, 'username': GLOBALS.postgres.auth.users.so_postgres_user.user, 'password': GLOBALS.postgres.auth.users.so_postgres_user.pass, 'adminUser': 'postgres', 'adminPassword': PG_ADMIN_PASS, 'dbname': 'securityonion', 'sslMode': 'require', 'assistantEnabled': true, 'esHostUrl': 'https://' ~ GLOBALS.manager_ip ~ ':9200', 'esUsername': GLOBALS.elasticsearch.auth.users.so_elastic_user.user, 'esPassword': GLOBALS.elasticsearch.auth.users.so_elastic_user.pass, 'esVerifyCert': false}}) %} -{% endif %} - {% do SOCDEFAULTS.soc.config.server.modules.influxdb.update({'hostUrl': 'https://' ~ GLOBALS.influxdb_host ~ ':8086'}) %} {% do SOCDEFAULTS.soc.config.server.modules.influxdb.update({'token': INFLUXDB_TOKEN}) %} {% for tool in SOCDEFAULTS.soc.config.server.client.tools %} From 3e02001544c0a9fade7e5172a407f8892b0b3386 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Wed, 29 Apr 2026 08:48:45 -0400 Subject: [PATCH 06/13] Open postgres port for import role in DOCKER-USER firewall When so-postgres was wired in (868cd1187), the import role's firewall defaults were missed while every other manager-class role (manager, managerhype, managersearch, standalone, eval) had postgres added to their DOCKER-USER manager-hostgroup portgroups. As a result, on a fresh import install the so-postgres container starts but tcp/5432 is dropped at DOCKER-USER, so soc/kratos/telegraf can't reach it. Add postgres alongside the existing influxdb entry so import nodes match the other roles. --- salt/firewall/defaults.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/salt/firewall/defaults.yaml b/salt/firewall/defaults.yaml index e9c82401d..9d0af3d0d 100644 --- a/salt/firewall/defaults.yaml +++ b/salt/firewall/defaults.yaml @@ -1482,6 +1482,7 @@ firewall: - kibana - redis - influxdb + - postgres - elasticsearch_rest - elasticsearch_node - elastic_agent_control From 82e55ae87f3d785358d2ac0992e9b302f3b8b3e1 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Wed, 29 Apr 2026 09:09:50 -0400 Subject: [PATCH 07/13] Open postgres on every hostgroup that opens influxdb The static defaults only listed postgres on each role's self-hostgroup, leaving sensor/searchnode/heavynode/receiver/fleet/idh/desktop/hypervisor hostgroups unable to reach the manager's so-postgres in distributed grids. A dynamic block in firewall/map.jinja added postgres to those hostgroups only when telegraf.output was switched to POSTGRES/BOTH, which left postgres unreachable by default. Mirror influxdb statically across manager/managerhype/managersearch/ standalone for every hostgroup that already lists influxdb, and drop the now-redundant telegraf-gated dynamic block from firewall/map.jinja. --- salt/firewall/defaults.yaml | 32 ++++++++++++++++++++++++++++++++ salt/firewall/map.jinja | 13 ------------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/salt/firewall/defaults.yaml b/salt/firewall/defaults.yaml index 9d0af3d0d..5c1229787 100644 --- a/salt/firewall/defaults.yaml +++ b/salt/firewall/defaults.yaml @@ -398,6 +398,7 @@ firewall: - elasticsearch_rest - docker_registry - influxdb + - postgres - sensoroni - yum - beats_5044 @@ -410,6 +411,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - beats_5044 @@ -427,6 +429,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - sensoroni searchnode: portgroups: @@ -437,6 +440,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -450,6 +454,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -459,6 +464,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -492,6 +498,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - elastic_agent_control @@ -502,6 +509,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -610,6 +618,7 @@ firewall: - elasticsearch_rest - docker_registry - influxdb + - postgres - sensoroni - yum - beats_5044 @@ -622,6 +631,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - beats_5044 @@ -639,6 +649,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - sensoroni searchnode: portgroups: @@ -649,6 +660,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -662,6 +674,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -671,6 +684,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -702,6 +716,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - elastic_agent_control @@ -712,6 +727,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -820,6 +836,7 @@ firewall: - elasticsearch_rest - docker_registry - influxdb + - postgres - sensoroni - yum - beats_5044 @@ -832,6 +849,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - beats_5044 @@ -849,6 +867,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - sensoroni searchnode: portgroups: @@ -858,6 +877,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -870,6 +890,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -879,6 +900,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -912,6 +934,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - elastic_agent_control @@ -922,6 +945,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -1040,6 +1064,7 @@ firewall: - elasticsearch_rest - docker_registry - influxdb + - postgres - sensoroni - yum - beats_5044 @@ -1052,6 +1077,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - beats_5044 @@ -1063,6 +1089,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - beats_5044 @@ -1074,6 +1101,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - redis @@ -1083,6 +1111,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - redis @@ -1093,6 +1122,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update @@ -1129,6 +1159,7 @@ firewall: portgroups: - docker_registry - influxdb + - postgres - sensoroni - yum - elastic_agent_control @@ -1139,6 +1170,7 @@ firewall: - yum - docker_registry - influxdb + - postgres - elastic_agent_control - elastic_agent_data - elastic_agent_update diff --git a/salt/firewall/map.jinja b/salt/firewall/map.jinja index 61f8215b8..58d8c189d 100644 --- a/salt/firewall/map.jinja +++ b/salt/firewall/map.jinja @@ -1,6 +1,5 @@ {% from 'vars/globals.map.jinja' import GLOBALS %} {% from 'docker/docker.map.jinja' import DOCKERMERGED %} -{% from 'telegraf/map.jinja' import TELEGRAFMERGED %} {% import_yaml 'firewall/defaults.yaml' as FIREWALL_DEFAULT %} {# add our ip to self #} @@ -56,16 +55,4 @@ {% endif %} -{# Open Postgres (5432) to minion hostgroups when Telegraf is configured to write to Postgres #} -{% set TG_OUT = TELEGRAFMERGED.output | upper %} -{% if TG_OUT in ['POSTGRES', 'BOTH'] %} -{% if role.startswith('manager') or role == 'standalone' or role == 'eval' %} -{% for r in ['sensor', 'searchnode', 'heavynode', 'receiver', 'fleet', 'idh', 'desktop', 'import'] %} -{% if FIREWALL_DEFAULT.firewall.role[role].chain["DOCKER-USER"].hostgroups[r] is defined %} -{% do FIREWALL_DEFAULT.firewall.role[role].chain["DOCKER-USER"].hostgroups[r].portgroups.append('postgres') %} -{% endif %} -{% endfor %} -{% endif %} -{% endif %} - {% set FIREWALL_MERGED = salt['pillar.get']('firewall', FIREWALL_DEFAULT.firewall, merge=True) %} From 2f01ce3b23a99faa1eb0cab11855aaeec377d0c2 Mon Sep 17 00:00:00 2001 From: Jorge Reyes <94730068+reyesj2@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:33:28 -0500 Subject: [PATCH 08/13] so-elastic-fleet-outputs-update now checks for cert drift. Remove running --cert arg on cert change to prevent highstate from running outputs-update 2x --- salt/elasticfleet/manager.sls | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/salt/elasticfleet/manager.sls b/salt/elasticfleet/manager.sls index 00fead9cf..1728f2010 100644 --- a/salt/elasticfleet/manager.sls +++ b/salt/elasticfleet/manager.sls @@ -18,17 +18,6 @@ so-elastic-fleet-auto-configure-logstash-outputs: - retry: attempts: 4 interval: 30 - -{# Separate from above in order to catch elasticfleet-logstash.crt changes and force update to fleet output policy #} -so-elastic-fleet-auto-configure-logstash-outputs-force: - cmd.run: - - name: /usr/sbin/so-elastic-fleet-outputs-update --certs - - retry: - attempts: 4 - interval: 30 - - onchanges: - - x509: etc_elasticfleet_logstash_crt - - x509: elasticfleet_kafka_crt {% endif %} # If enabled, automatically update Fleet Server URLs & ES Connection From 39d09471026443258f604d54106509d5c9226c83 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:38:40 -0500 Subject: [PATCH 09/13] update default elastic agent logging level to warning --- .../tools/sbin/so-elastic-fleet-common | 2 +- salt/manager/tools/sbin/soup | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common index 92532082a..91fa787f2 100644 --- a/salt/elasticfleet/tools/sbin/so-elastic-fleet-common +++ b/salt/elasticfleet/tools/sbin/so-elastic-fleet-common @@ -240,7 +240,7 @@ elastic_fleet_policy_create() { --arg DESC "$DESC" \ --arg TIMEOUT $TIMEOUT \ --arg FLEETSERVER "$FLEETSERVER" \ - '{"name": $NAME,"id":$NAME,"description":$DESC,"namespace":"default","monitoring_enabled":["logs"],"inactivity_timeout":$TIMEOUT,"has_fleet_server":$FLEETSERVER}' + '{"name": $NAME,"id":$NAME,"description":$DESC,"namespace":"default","monitoring_enabled":["logs"],"inactivity_timeout":$TIMEOUT,"has_fleet_server":$FLEETSERVER,"advanced_settings":{"agent_logging_level": "warning"}}' ) # Create Fleet Policy if ! fleet_api "agent_policies" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then diff --git a/salt/manager/tools/sbin/soup b/salt/manager/tools/sbin/soup index a838e3275..44eda49bb 100755 --- a/salt/manager/tools/sbin/soup +++ b/salt/manager/tools/sbin/soup @@ -485,6 +485,54 @@ elasticsearch_backup_index_templates() { tar -czf /nsm/backup/3.0.0_elasticsearch_index_templates.tar.gz -C /opt/so/conf/elasticsearch/templates/index/ . } +elasticfleet_set_agent_logging_level_warn() { + . /usr/sbin/so-elastic-fleet-common + + local current_agent_policies + if ! current_agent_policies=$(fleet_api "agent_policies?perPage=1000"); then + echo "Warning: unable to retrieve Fleet agent policies" + return 0 + fi + + # Only updating policies that are within Security Onion defaults and do not already have any user configured advanced_settings. + local policies_to_update + policies_to_update=$(jq -c ' + .items[] + | select(has("advanced_settings") | not) + | select( + .id == "so-grid-nodes_general" + or .id == "so-grid-nodes_heavy" + or .id == "endpoints-initial" + or (.id | startswith("FleetServer_")) + ) + ' <<< "$current_agent_policies") + + if [[ -z "$policies_to_update" ]]; then + return 0 + fi + + while IFS= read -r policy; do + [[ -z "$policy" ]] && continue + + local policy_id policy_name policy_namespace + policy_id=$(jq -r '.id' <<< "$policy") + policy_name=$(jq -r '.name' <<< "$policy") + policy_namespace=$(jq -r '.namespace' <<< "$policy") + + local update_logging + update_logging=$(jq -n \ + --arg name "$policy_name" \ + --arg namespace "$policy_namespace" \ + '{name: $name, namespace: $namespace, advanced_settings: {agent_logging_level: "warning"}}' + ) + + echo "Setting elastic agent_logging_level to warning on policy '$policy_name' ($policy_id)." + if ! fleet_api "agent_policies/$policy_id" -XPUT -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$update_logging" >/dev/null; then + echo " warning: failed to update agent policy '$policy_name' ($policy_id)" >&2 + fi + done <<< "$policies_to_update" +} + ensure_postgres_local_pillar() { # Postgres was added as a service after 3.0.0, so the new pillar/top.sls # references postgres.soc_postgres / postgres.adv_postgres unconditionally. @@ -553,6 +601,9 @@ post_to_3.1.0() { # file_roots of its own and --local would fail with "No matching sls found". salt-call state.apply postgres.telegraf_users queue=True || true + # Update default agent policies to use logging level warn. + elasticfleet_set_agent_logging_level_warn || true + POSTVERSION=3.1.0 } From 86966d277889e535f400901175d9200cda318f4f Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Fri, 1 May 2026 12:44:08 -0500 Subject: [PATCH 10/13] reauthorize unhealthy transform jobs using kibana 9.3.3 auth flow --- salt/manager/tools/sbin/soup | 79 ++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/salt/manager/tools/sbin/soup b/salt/manager/tools/sbin/soup index 44eda49bb..c0f8b61c1 100755 --- a/salt/manager/tools/sbin/soup +++ b/salt/manager/tools/sbin/soup @@ -533,6 +533,82 @@ elasticfleet_set_agent_logging_level_warn() { done <<< "$policies_to_update" } +check_transform_health_and_reauthorize() { + . /usr/sbin/so-elastic-fleet-common + + echo "Checking integration transform jobs for unhealthy / unauthorized status..." + + local transforms_doc stats_doc installed_doc + if ! transforms_doc=$(so-elasticsearch-query "_transform/_all?size=1000" --fail --retry 3 --retry-delay 5 2>/dev/null); then + echo "Unable to query for transform jobs, skipping reauthorization." + return 0 + fi + if ! stats_doc=$(so-elasticsearch-query "_transform/_all/_stats?size=1000" --fail --retry 3 --retry-delay 5 2>/dev/null); then + echo "Unable to query for transform job stats, skipping reauthorization." + return 0 + fi + if ! installed_doc=$(fleet_api "epm/packages/installed?perPage=500"); then + echo "Unable to list installed Fleet packages, skipping reauthorization." + return 0 + fi + + # Get all transforms that meet the following + # - unhealthy (any non-green health status) + # - metadata has run_as_kibana_system: false (this fix is specific to transforms started prior to Kibana 9.3.3) + # - are not orphaned (integration is not somehow missing/corrupt/uninstalled) + local unhealthy_transforms + unhealthy_transforms=$(jq -c -n \ + --argjson t "$transforms_doc" \ + --argjson s "$stats_doc" \ + --argjson i "$installed_doc" ' + ($i.items | map({key: .name, value: .version}) | from_entries) as $pkg_ver + | ($s.transforms | map({key: .id, value: .health.status}) | from_entries) as $health + | [ $t.transforms[] + | select(._meta.run_as_kibana_system == false) + | select(($health[.id] // "unknown") != "green") + | {id, pkg: ._meta.package.name, ver: ($pkg_ver[._meta.package.name])} + ] + | if length == 0 then empty else . end + | (map(select(.ver == null)) | map({orphan: .id})[]), + (map(select(.ver != null)) + | group_by(.pkg) + | map({pkg: .[0].pkg, ver: .[0].ver, transformIds: map(.id)})[]) + ') + + if [[ -z "$unhealthy_transforms" ]]; then + return 0 + fi + + local unhealthy_count + unhealthy_count=$(jq -s '[.[].transformIds? // empty | .[]] | length' <<< "$unhealthy_transforms") + echo "Found $unhealthy_count transform(s) needing reauthorization." + + local total_failures=0 + while IFS= read -r transform; do + [[ -z "$transform" ]] && continue + if jq -e 'has("orphan")' <<< "$transform" >/dev/null 2>&1; then + echo "Skipping transform not owned by any installed Fleet package: $(jq -r '.orphan' <<< "$transform")" + continue + fi + + local pkg ver body resp + pkg=$(jq -r '.pkg' <<< "$transform") + ver=$(jq -r '.ver' <<< "$transform") + body=$(jq -c '{transforms: (.transformIds | map({transformId: .}))}' <<< "$transform") + + echo "Reauthorizing transform(s) for ${pkg}-${ver}..." + resp=$(fleet_api "epm/packages/${pkg}/${ver}/transforms/authorize" \ + -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' \ + -d "$body") || { echo "Could not reauthorize transform(s) for ${pkg}-${ver}"; continue; } + + (( total_failures += $(jq 'map(select(.success != true)) | length' <<< "$resp" 2>/dev/null) )) + done <<< "$unhealthy_transforms" + + if [[ "$total_failures" -gt 0 ]]; then + echo "Some transform(s) failed to reauthorize." + fi +} + ensure_postgres_local_pillar() { # Postgres was added as a service after 3.0.0, so the new pillar/top.sls # references postgres.soc_postgres / postgres.adv_postgres unconditionally. @@ -604,6 +680,9 @@ post_to_3.1.0() { # Update default agent policies to use logging level warn. elasticfleet_set_agent_logging_level_warn || true + # Check for unhealthy / unauthorized integration transform jobs and attempt reauthorizations + check_transform_health_and_reauthorize || true + POSTVERSION=3.1.0 } From 702b3585ccf4fe99e17154d8e8d48f2d368798f2 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Fri, 1 May 2026 12:57:59 -0500 Subject: [PATCH 11/13] excluding additional integration transform job failures --- salt/common/tools/sbin/so-log-check | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/common/tools/sbin/so-log-check b/salt/common/tools/sbin/so-log-check index f355e1bfe..a3d9c51d0 100755 --- a/salt/common/tools/sbin/so-log-check +++ b/salt/common/tools/sbin/so-log-check @@ -227,7 +227,7 @@ if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then EXCLUDED_ERRORS="$EXCLUDED_ERRORS|from NIC checksum offloading" # zeek reporter.log EXCLUDED_ERRORS="$EXCLUDED_ERRORS|marked for removal" # docker container getting recycled EXCLUDED_ERRORS="$EXCLUDED_ERRORS|tcp 127.0.0.1:6791: bind: address already in use" # so-elastic-fleet agent restarting. Seen starting w/ 8.18.8 https://github.com/elastic/kibana/issues/201459 - EXCLUDED_ERRORS="$EXCLUDED_ERRORS|TransformTask\] \[logs-(tychon|aws_billing|microsoft_defender_endpoint|armis|o365_metrics|microsoft_sentinel|snyk).*user so_kibana lacks the required permissions \[(logs|metrics)-\1" # Known issue with integrations starting transform jobs that are explicitly not allowed to start as a system user. (installed as so_elastic / so_kibana) + EXCLUDED_ERRORS="$EXCLUDED_ERRORS|TransformTask\] \[logs-(tychon|aws_billing|microsoft_defender_endpoint|armis|o365_metrics|microsoft_sentinel|snyk|cyera|island_browser).*user so_kibana lacks the required permissions \[(logs|metrics)-\1" # Known issue with integrations starting transform jobs that are explicitly not allowed to start as a system user. This error should not be seen on fresh ES 9.3.3 installs or after SO 3.1.0 with soups addition of check_transform_health_and_reauthorize() EXCLUDED_ERRORS="$EXCLUDED_ERRORS|manifest unknown" # appears in so-dockerregistry log for so-tcpreplay following docker upgrade to 29.2.1-1 fi From 2203037ce75fdf668340eae7ba41ab99b2af8bc5 Mon Sep 17 00:00:00 2001 From: reyesj2 <94730068+reyesj2@users.noreply.github.com> Date: Mon, 4 May 2026 10:52:37 -0500 Subject: [PATCH 12/13] fleet package registry health check --- salt/elastic-fleet-package-registry/enabled.sls | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/salt/elastic-fleet-package-registry/enabled.sls b/salt/elastic-fleet-package-registry/enabled.sls index e2833f5be..a5a8b9d22 100644 --- a/salt/elastic-fleet-package-registry/enabled.sls +++ b/salt/elastic-fleet-package-registry/enabled.sls @@ -51,6 +51,16 @@ so-elastic-fleet-package-registry: - {{ ULIMIT.name }}={{ ULIMIT.soft }}:{{ ULIMIT.hard }} {% endfor %} {% endif %} + +wait_for_so-elastic-fleet-package-registry: + http.wait_for_successful_query: + - name: "http://localhost:8080/health" + - status: 200 + - wait_for: 300 + - request_interval: 15 + - require: + - docker_container: so-elastic-fleet-package-registry + delete_so-elastic-fleet-package-registry_so-status.disabled: file.uncomment: - name: /opt/so/conf/so-status/so-status.conf From b701664e04cb3d69d395cc6c178c7ef3d16289c9 Mon Sep 17 00:00:00 2001 From: Mike Reeves Date: Mon, 4 May 2026 12:09:35 -0400 Subject: [PATCH 13/13] Fix unsafe PyYAML load in filecheck --- salt/strelka/filecheck/filecheck | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/strelka/filecheck/filecheck b/salt/strelka/filecheck/filecheck index 758248083..35b47ce71 100644 --- a/salt/strelka/filecheck/filecheck +++ b/salt/strelka/filecheck/filecheck @@ -15,7 +15,7 @@ from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler with open("/opt/so/conf/strelka/filecheck.yaml", "r") as ymlfile: - cfg = yaml.load(ymlfile, Loader=yaml.Loader) + cfg = yaml.safe_load(ymlfile) extract_path = cfg["filecheck"]["extract_path"] historypath = cfg["filecheck"]["historypath"]