From 087d23eeafeb9d96028f22d6e6ecc136d1129319 Mon Sep 17 00:00:00 2001 From: alexbelgium Date: Tue, 14 Jul 2026 12:11:26 +0200 Subject: [PATCH 1/2] fix(elasticsearch): stay root at runtime, fix upgrade permission failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported: on upgrade from an existing 7.17.9 install, the add-on failed to start with "mv: cannot move '/data/config' to '/data/config.bak-7.17.9': Permission denied". Root cause: a previous fix in this same release restored `USER 1000:0` at the end of the Dockerfile to match the upstream base image's own final USER directive. But the upstream 8.19 entrypoint no longer drops privileges itself (confirmed: it execs elasticsearch directly, no gosu/chroot dance), and existing installs have /data owned by root (7.17.9's default image variant runs fully as root). A non-root container can never chown or move that data. Revert to root at runtime, matching how this add-on always ran and matching its own AppArmor profile (chown, setuid, setgid, sys_chroot, mount capabilities — all meaningless for a non-root process anyway). Root stays required for the build-time entrypoint patch too, unchanged from the prior fix. Co-Authored-By: Claude Sonnet 5 --- elasticsearch/CHANGELOG.md | 3 ++- elasticsearch/Dockerfile | 8 ++++++-- elasticsearch/rootfs/usr/local/bin/addon-init.sh | 5 +++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/elasticsearch/CHANGELOG.md b/elasticsearch/CHANGELOG.md index 9ea78d5768..72567495de 100644 --- a/elasticsearch/CHANGELOG.md +++ b/elasticsearch/CHANGELOG.md @@ -8,8 +8,9 @@ - Removed the `ingest-attachment` plugin install: it is a bundled module since Elasticsearch 8.0. - Startup persistence logic rewritten as a proper init script (`/usr/local/bin/addon-init.sh`) instead of line-number-based entrypoint patching. - Added `updater.json` so upstream 8.19.x releases are tracked automatically (pinned to the 8.19 line: 9.x cannot read indices created in 7.x). -- The upstream 8.x image ends the build as a non-root user with a read-only entrypoint; the Dockerfile now switches to root for the build steps that patch/install into it and restores the Elasticsearch user before runtime. +- The upstream 8.x image ends the build as a non-root user with a read-only entrypoint; the Dockerfile now switches to root for the build steps that patch/install into it. The image stays root at runtime too (unchanged from 7.17.9): the upstream entrypoint no longer drops privileges itself, and `addon-init.sh` needs to chown/move pre-existing `/data` content that may be owned by root from earlier installs. - `env_vars` names starting with a digit are now rejected before export instead of crashing the entrypoint. +- Fixed a startup failure (`mv: cannot move '/data/config' ... Permission denied`) on upgrade from an existing 7.17.9 install, caused by an earlier fix in this same release that switched the runtime user to non-root before this fix was in place. ## 8.14.3-3 (2026-06-19) - Fix startup failing with `chroot: cannot change root directory` by allowing `capability sys_chroot` in the AppArmor profile (#2709) diff --git a/elasticsearch/Dockerfile b/elasticsearch/Dockerfile index 4295ea3e0e..0f5edec8d2 100644 --- a/elasticsearch/Dockerfile +++ b/elasticsearch/Dockerfile @@ -141,5 +141,9 @@ HEALTHCHECK \ --timeout=25s \ CMD curl -A "HealthCheck: Docker/1.0" -s -f "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" &>/dev/null || exit 1 -# Restore the Elasticsearch user for runtime, matching the base image -USER 1000:0 +# Stay root at runtime: addon-init.sh must chown/mv pre-existing /data +# content that may be owned by root from earlier installs, and the upstream +# entrypoint no longer drops privileges itself, so a non-root container +# can't touch that data at all. This matches the addon's own AppArmor +# profile (chown, setuid, setgid, sys_chroot, mount capabilities), which +# assumes a root process. diff --git a/elasticsearch/rootfs/usr/local/bin/addon-init.sh b/elasticsearch/rootfs/usr/local/bin/addon-init.sh index ff91909e0a..c7fd67ec8f 100755 --- a/elasticsearch/rootfs/usr/local/bin/addon-init.sh +++ b/elasticsearch/rootfs/usr/local/bin/addon-init.sh @@ -1,8 +1,9 @@ #!/bin/bash # shellcheck shell=bash # Sourced by /usr/local/bin/docker-entrypoint.sh (right after "set -e"), -# before Elasticsearch starts. Runs as root when the Supervisor starts the -# container; the official entrypoint then drops privileges itself. +# before Elasticsearch starts. Runs as root (the image stays root at +# runtime - see Dockerfile); the official entrypoint does not drop +# privileges itself, and Elasticsearch ends up running as root too. # # Responsibilities: # 1. Export user env_vars from /data/options.json From a313475d928058158b71121ab83f47f352239c8e Mon Sep 17 00:00:00 2001 From: alexbelgium Date: Tue, 14 Jul 2026 12:33:26 +0200 Subject: [PATCH 2/2] fix(elasticsearch): drop to uid 1000 before starting Elasticsearch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Elasticsearch refuses to bootstrap as root ("can not run elasticsearch as root"). The previous fix in this PR kept the container root at runtime to fix the /data permission failure, but never dropped privileges again afterward — unlike 7.17.9, whose own entrypoint used `chroot --userspec=1000:0` before launching Elasticsearch, the upstream 8.x entrypoint no longer does that. So every start, fresh or upgrade, would fail once addon-init.sh's setup finished. Fix: after addon-init.sh completes its root-only work (migration guard, data/config relocation, chown), it re-execs the entrypoint itself as uid 1000 via `chroot --userspec=1000:0 / ...` — the same mechanism 7.17.9 used, and exactly what the add-on's AppArmor profile already grants (sys_chroot, setuid, setgid). On the re-exec'd pass the script returns immediately (guarded by an exported sentinel) so none of the setup work repeats; exported env vars (env_vars, the security default) survive the exec normally. Co-Authored-By: Claude Sonnet 5 --- elasticsearch/CHANGELOG.md | 3 +- elasticsearch/Dockerfile | 13 ++++---- .../rootfs/usr/local/bin/addon-init.sh | 30 +++++++++++++++++-- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/elasticsearch/CHANGELOG.md b/elasticsearch/CHANGELOG.md index 72567495de..1ed6b45fd4 100644 --- a/elasticsearch/CHANGELOG.md +++ b/elasticsearch/CHANGELOG.md @@ -8,9 +8,10 @@ - Removed the `ingest-attachment` plugin install: it is a bundled module since Elasticsearch 8.0. - Startup persistence logic rewritten as a proper init script (`/usr/local/bin/addon-init.sh`) instead of line-number-based entrypoint patching. - Added `updater.json` so upstream 8.19.x releases are tracked automatically (pinned to the 8.19 line: 9.x cannot read indices created in 7.x). -- The upstream 8.x image ends the build as a non-root user with a read-only entrypoint; the Dockerfile now switches to root for the build steps that patch/install into it. The image stays root at runtime too (unchanged from 7.17.9): the upstream entrypoint no longer drops privileges itself, and `addon-init.sh` needs to chown/move pre-existing `/data` content that may be owned by root from earlier installs. +- The upstream 8.x image ends the build as a non-root user with a read-only entrypoint; the Dockerfile now switches to root for the build steps that patch/install into it. The container also starts as root (unchanged from 7.17.9) so `addon-init.sh` can chown/move pre-existing `/data` content that may be owned by root from earlier installs; unlike 7.17.9's own entrypoint, the upstream 8.x entrypoint no longer drops privileges before starting Elasticsearch (which refuses to run as root), so `addon-init.sh` now does that itself via `chroot --userspec=1000:0` once its root-only work is done. - `env_vars` names starting with a digit are now rejected before export instead of crashing the entrypoint. - Fixed a startup failure (`mv: cannot move '/data/config' ... Permission denied`) on upgrade from an existing 7.17.9 install, caused by an earlier fix in this same release that switched the runtime user to non-root before this fix was in place. +- Fixed a second regression from that same fix: without a privilege drop before starting Elasticsearch, both fresh installs and upgrades would fail Elasticsearch's own root-check ("can not run elasticsearch as root"). ## 8.14.3-3 (2026-06-19) - Fix startup failing with `chroot: cannot change root directory` by allowing `capability sys_chroot` in the AppArmor profile (#2709) diff --git a/elasticsearch/Dockerfile b/elasticsearch/Dockerfile index 0f5edec8d2..b5e7117821 100644 --- a/elasticsearch/Dockerfile +++ b/elasticsearch/Dockerfile @@ -141,9 +141,10 @@ HEALTHCHECK \ --timeout=25s \ CMD curl -A "HealthCheck: Docker/1.0" -s -f "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" &>/dev/null || exit 1 -# Stay root at runtime: addon-init.sh must chown/mv pre-existing /data -# content that may be owned by root from earlier installs, and the upstream -# entrypoint no longer drops privileges itself, so a non-root container -# can't touch that data at all. This matches the addon's own AppArmor -# profile (chown, setuid, setgid, sys_chroot, mount capabilities), which -# assumes a root process. +# Start as root: addon-init.sh needs it to chown/move pre-existing /data +# content that may be owned by root from earlier installs. It drops to +# uid 1000 itself (via chroot --userspec) before Elasticsearch actually +# starts, since Elasticsearch refuses to run as root and the upstream 8.x +# entrypoint no longer does that drop on its own (7.x's did). This matches +# the addon's own AppArmor profile (chown, setuid, setgid, sys_chroot, +# mount capabilities). diff --git a/elasticsearch/rootfs/usr/local/bin/addon-init.sh b/elasticsearch/rootfs/usr/local/bin/addon-init.sh index c7fd67ec8f..66a06d36ff 100755 --- a/elasticsearch/rootfs/usr/local/bin/addon-init.sh +++ b/elasticsearch/rootfs/usr/local/bin/addon-init.sh @@ -1,15 +1,25 @@ #!/bin/bash # shellcheck shell=bash # Sourced by /usr/local/bin/docker-entrypoint.sh (right after "set -e"), -# before Elasticsearch starts. Runs as root (the image stays root at -# runtime - see Dockerfile); the official entrypoint does not drop -# privileges itself, and Elasticsearch ends up running as root too. +# before Elasticsearch starts. The container starts as root (see +# Dockerfile) so this script can chown/move pre-existing /data content +# that may be owned by root from earlier installs. Elasticsearch itself +# refuses to run as root, and unlike 7.x the upstream 8.x entrypoint no +# longer drops privileges on its own, so this script does it at the end +# (section 6) by re-execing the entrypoint as uid 1000. On that re-exec'd +# pass this script just returns immediately (see the guard right below). # # Responsibilities: # 1. Export user env_vars from /data/options.json # 2. Default xpack.security.enabled=false (7.x behavior) unless user overrides # 3. Relocate data & config to /data for persistence (idempotent) # 4. Guard major-version data migrations (7.x -> 8.x is automatic) +# 5. Record the running version once Elasticsearch is confirmed healthy +# 6. Drop root privileges before Elasticsearch actually starts + +if [ -n "${_ADDON_INIT_REEXEC:-}" ]; then + return 0 +fi echo "-----------------------------------------------------------" echo " Add-on: Elasticsearch server" @@ -141,3 +151,17 @@ if [ "$data_version" != "$current_version" ]; then done ) & fi + +############################ +# 6 Drop privileges # +############################ + +# Elasticsearch refuses to start as root ("can not run elasticsearch as +# root"). 7.x's own entrypoint dropped to uid 1000 via chroot before +# launching Elasticsearch; 8.x no longer does that, so do it here instead, +# then let the entrypoint continue as uid 1000 (matches the sys_chroot / +# setuid / setgid capabilities already granted in the AppArmor profile). +if [ "$(id -u)" -eq 0 ]; then + export _ADDON_INIT_REEXEC=1 + exec chroot --userspec=1000:0 / /usr/local/bin/docker-entrypoint.sh "$@" +fi