debugging Chainguard distroless containers

Shi
CI/CD/DevOps
Published in
2 min readFeb 1, 2024

--

Chainguard images are lightweight, distroless images with minimum footprint, reduced attack surface and much less known vulnerabilities; so it serves as good starting point as a base image. Further, they provide both the dev and prod version of the most commonly used tech stack like java, python, php, etc. so i started to explore how to tap into it for my daily work.

I quickly realized that working with distroless images has its own pain and lacking of shell access is a top concern, and your usual tactics like ‘docker exec’ wouldn’t work by default, so I am exploring any possible workaround and I find the following master piece at https://iximiuz.com/en/posts/docker-debug-slim-containers/

my image dockerfile

FROM cgr.dev/chainguard/jre:latest-dev AS build
LABEL NAME = "WebGoat: built based on latest ChainGuard JRE image"

USER root

WORKDIR /tmp
RUN apk update && apk add curl
ENV URL=https://xxx.com
RUN curl -k -o agent.zip "${URL}/binaries/JAVA"
RUN mkdir -p /tmp/agent && unzip -d /tmp/agent agent.zip

WORKDIR /home/webgoat
RUN wget https://github.com/WebGoat/WebGoat/releases/download/v2023.4/webgoat-2023.4.jar -O /home/webgoat/webgoat.jar

FROM cgr.dev/chainguard/jre:latest
USER root
WORKDIR /home/webgoat
COPY --from=build /home/webgoat/webgoat.jar /home/webgoat/webgoat.jar
COPY --from=build /tmp/agent/agent.jar /tmp/agent/agent.jar

ENV JAVA_TOOL_OPTIONS="-javaagent:/tmp/agent/agent.jar"

ENTRYPOINT [ "java", \
"-Duser.home=/home/webgoat", \
"-Dfile.encoding=UTF-8", \
"--add-opens", "java.base/java.lang=ALL-UNNAMED", \
"--add-opens", "java.base/java.util=ALL-UNNAMED", \
"--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", \
"--add-opens", "java.base/java.text=ALL-UNNAMED", \
"--add-opens", "java.desktop/java.beans=ALL-UNNAMED", \
"--add-opens", "java.desktop/java.awt.font=ALL-UNNAMED", \
"--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", \
"--add-opens", "java.base/java.io=ALL-UNNAMED", \
"--add-opens", "java.base/java.util=ALL-UNNAMED", \
"--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", \
"--add-opens", "java.base/java.io=ALL-UNNAMED", \
"-Drunning.in.docker=true", \
"-Dwebgoat.host=0.0.0.0", \
"-Dwebwolf.host=0.0.0.0", \
"-Dwebgoat.port=8080", \
"-Dwebwolf.port=9090", \
"-jar", "webgoat.jar" ]

so here we can easily leverage the dev image to install some utilities using apt and download some utilities and then copy to the prod images and ship out, and then we can launch the container with

docker run -it -p 8080:8080 --name target wg-undistro

so how do we that the process is actually running and listening on correct network ports?

our usual docker exec tactics woudn’t work

# docker exec aef751eec466 sh
OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH: unknown

we could acheive this by using the approach mentioned above

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aef751eec466 wg-undistro "java -Duser.home=/h…" 4 seconds ago Up 4 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp target


# docker run --rm -it --name debugger --pid container:target --network container:target busybox sh
/ # hostname
aef751eec466

/ # netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:9090 0.0.0.0:* LISTEN 1/java
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 1/java

/ # ps aux
PID USER TIME COMMAND
1 root 2:10 java -Duser.home=/home/webgoat -Dfile.encoding=UTF-8 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --
123 root 0:00 sh
131 root 0:00 ps aux


/ # ls /proc/1/root/home
java webgoat
/ # ls /proc/1/root/home/webgoat/
webgoat.jar

--

--

Shi
CI/CD/DevOps

I am a coder/engineer/application security specialist. I like to play around with language and tools; I have strong interest in efficiency improvement.