From a313475d928058158b71121ab83f47f352239c8e Mon Sep 17 00:00:00 2001 From: alexbelgium Date: Tue, 14 Jul 2026 12:33:26 +0200 Subject: [PATCH] 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