Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions telegraf/1.39/distroless/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Telegraf on scratch: the static (CGO_ENABLED=0) binary plus only the runtime
# files it needs — no shell, package manager, or OS userland. The alpine stage
# GPG-verifies the release and assembles the exact final image tree under
# /rootfs; the scratch stage is a single COPY of it.

ARG TELEGRAF_VERSION=1.39.1

FROM alpine:3.23 AS fetch
ARG TELEGRAF_VERSION

RUN set -eux; \
case "$(apk --print-arch)" in \
x86_64) ARCH='amd64';; \
aarch64) ARCH='arm64';; \
*) echo "Unsupported architecture: $(apk --print-arch)" >&2; exit 1;; \
esac; \
apk add --no-cache ca-certificates tzdata wget gnupg tar; \
update-ca-certificates; \
mkdir -p ~/.gnupg; echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf; \
gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys 24C975CBA61A024EE1B631787C3D57159FC2F927; \
base="telegraf-${TELEGRAF_VERSION}_linux_${ARCH}.tar.gz"; \
wget --no-verbose "https://dl.influxdata.com/telegraf/releases/${base}"; \
wget --no-verbose "https://dl.influxdata.com/telegraf/releases/${base}.asc"; \
gpg --batch --verify "${base}.asc" "${base}"; \
mkdir -p /src /rootfs/usr/bin /rootfs/etc/telegraf /rootfs/etc/ssl/certs /rootfs/usr/share; \
tar -C /src -xzf "${base}"; \
# Copy from the explicit `telegraf-<v>/` prefix: release tar roots vary (some
# add a leading ./), which makes a fixed --strip-components unreliable.
src="/src/telegraf-${TELEGRAF_VERSION}"; \
cp -a "${src}/usr/bin/telegraf" /rootfs/usr/bin/telegraf; \
cp -a "${src}/etc/telegraf/telegraf.conf" /rootfs/etc/telegraf/telegraf.conf; \
cp -a "${src}/etc/telegraf/telegraf.d" /rootfs/etc/telegraf/telegraf.d; \
cp /etc/ssl/certs/ca-certificates.crt /rootfs/etc/ssl/certs/ca-certificates.crt; \
cp -a /usr/share/zoneinfo /rootfs/usr/share/zoneinfo; \
# Resolve hostnames via /etc/hosts before DNS.
printf 'hosts: files dns\n' > /rootfs/etc/nsswitch.conf; \
# Lets pure-Go os/user (CGO_ENABLED=0) map uid 65532 to a name; without it
# lookups like procstat's user tag fail silently. USER below sets the identity.
# We run as 65532 (the distroless "nonroot" uid), NOT 65534/nobody: 65534 is the
# kernel overflow uid — the id that unmapped user-namespace ids and NFS root-squash
# collapse to — so running as it would make telegraf indistinguishable from a
# failed-to-map process to security/monitoring tooling. 65532 sits outside that.
# overflow uid default 65534: https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html
# unmapped userns id -> overflow: https://man7.org/linux/man-pages/man7/user_namespaces.7.html
# distroless nonroot = 65532: https://github.com/GoogleContainerTools/distroless/blob/main/common/variables.bzl
# (nobody is kept in the map so 65534-owned files still resolve; we just never run as it.)
printf 'root:x:0:0:root:/root:/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin\nnonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin\n' > /rootfs/etc/passwd; \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would name the uid 65532 entry "telegraf" rather than "nonroot" (keeping the numeric USER and uid exactly as they are). The name is about to become a contract: procstat's user tag, anything else resolving the uid, and our docs all say "telegraf" in the other variants, and renaming after release is a visible behavior change. "nonroot" buys familiarity with the distroless ecosystem, but for a telegraf image I think parity with the siblings wins. Happy to be argued out of it, but let's decide now rather than after the tag exists. @srebhan please let me know you thoughts on this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also prefer telegraf if this doesn't collide with k8s conventions of "non-root".

printf 'root:x:0:\nnobody:x:65534:\nnonroot:x:65532:\n' > /rootfs/etc/group; \
Comment thread
srebhan marked this conversation as resolved.
# $HOME and a writable /tmp for 65532. We set ownership, not mode bits like
# 1777: buildah's COPY preserves a dir's ownership but not its mode. Numeric id,
# not "nonroot": this chown runs in the alpine stage, whose /etc/passwd has no
# nonroot user, so a BusyBox name lookup would fail ("unknown user/group") and
# abort under set -eux. A numeric id skips the passwd lookup entirely.
# busybox chown name lookup + err string (xget_uidgid; mirror linked from busybox.net/source.html):
# https://github.com/vda-linux/busybox_mirror/blob/ec0c5cc142f1f9ea57235df5d093fbe180ad9c7d/libpwdgrp/uidgid_get.c#L77-L80
mkdir -p /rootfs/home/nonroot /rootfs/tmp; chown 65532:65532 /rootfs/home/nonroot /rootfs/tmp
Comment thread
srebhan marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth a note in the image docs: /tmp and /home/nonroot are writable only by uid 65532, so runtimes that assign an arbitrary uid (OpenShift's default SCC, an explicit runAsUser) get no writable /tmp or HOME, unlike the siblings whose base images ship /tmp as 1777. Given the buildah mode-preservation caveat you documented, I think documenting the constraint is the right call rather than fighting it, but it should be written down where users will find it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is really: How about for buildx? If any of those does preserve the mode I would set it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildx does preserve it, mode and ownership both. I built the same shape the Dockerfile uses:

FROM alpine:3.23 AS fetch
RUN mkdir -p /rootfs/tmp /rootfs/home/nonroot; \
    chmod 1777 /rootfs/tmp; chmod 0750 /rootfs/home/nonroot; \
    chown 65532:65532 /rootfs/tmp /rootfs/home/nonroot
FROM scratch
COPY --from=fetch /rootfs/ /

Exporting the final image (buildx v0.25.0) gives:

drwxrwxrwt  65532 65532  tmp/
drwxr-x---  65532 65532  home/nonroot/

So the sticky bit survives, and since official-images publishes through BuildKit that is the path that matters here. Worth setting chmod 1777 on /rootfs/tmp: it takes effect under buildx, and under buildah it is a no-op that costs nothing.

That also closes something I was going to raise separately. As it stands /tmp is writable only by uid 65532, so a runtime that assigns an arbitrary uid (OpenShift's default SCC, an explicit runAsUser) gets no writable /tmp, unlike the alpine and debian variants whose bases ship /tmp as 1777. With the mode set, that difference goes away rather than needing a caveat in the docs.

I only verified the buildx half, so I can't speak to whether the buildah behavior in the comment is accurate. Either way the comment needs a reword, since as written it reads as "mode bits do not survive COPY" in general when it is specific to the builder we do not publish with.


FROM scratch

ARG TELEGRAF_VERSION
ENV TELEGRAF_VERSION=${TELEGRAF_VERSION}
LABEL org.opencontainers.image.title="telegraf" \
org.opencontainers.image.description="Distroless Telegraf — static binary on scratch (no shell, no OS userland, non-root)" \
org.opencontainers.image.version="${TELEGRAF_VERSION}" \
org.opencontainers.image.source="https://github.com/influxdata/influxdata-docker" \
org.opencontainers.image.base.name="scratch" \
org.opencontainers.image.vendor="InfluxData Inc." \
org.opencontainers.image.licenses="MIT"

# The whole rootfs was staged and GPG-verified in the fetch stage; one COPY
# brings it over. Ownership (incl. the 65532-owned /home/nonroot and /tmp) is
# preserved by docker, podman, and buildah alike.
COPY --from=fetch /rootfs/ /

ENV HOME=/home/nonroot

# Numeric, not a name: with runAsNonRoot=true the kubelet reads the image's USER
# field directly (never its /etc/passwd), so a named user fails admission
# ("cannot verify user is non-root"). 65532 is the distroless "nonroot" uid.
# https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kuberuntime/security_context_others.go#L50
USER 65532:65532

ENTRYPOINT ["/usr/bin/telegraf"]
Comment thread
skartikey marked this conversation as resolved.
CMD ["--config", "/etc/telegraf/telegraf.conf", "--config-directory", "/etc/telegraf/telegraf.d"]
10 changes: 10 additions & 0 deletions telegraf/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@
"amd64",
"arm64v8"
]
},
{
"name": "distroless",
"versions": [
"1.39"
],
"architectures": [
"amd64",
"arm64v8"
]
}
]
}