From cef3aec300304db4b06da6d6f99dd1ec0e049258 Mon Sep 17 00:00:00 2001 From: Go Kudo Date: Fri, 5 Jul 2024 00:53:57 +0900 Subject: [PATCH] initial --- .dockerignore | 5 ++ .github/workflows/ci.yml | 37 +++++++++++++ Dockerfile | 41 ++++++++++++++ README.md | 19 +++++++ dependency_resolve | 115 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml create mode 100644 Dockerfile create mode 100644 README.md create mode 100755 dependency_resolve diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ab8cdec --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.github +*.DS_Store +LICENSE +README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..19e8dcd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + workflow_dispatch: + pull_request_target: + push: + branches: + - main + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + matrix: + arch: ["amd64", "arm64/v8", "arm/v7"] + busybox: ["glibc", "musl", "uclibc"] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64,arm + - name: Setup buildx + uses: docker/setup-buildx-action@v3 + - name: Build container + uses: docker/build-push-action@v6 + with: + build-args: ARCH=${{ matrix.arch }} + context: . + file: ./Dockerfile + load: true + push: false + tags: test-${{ matrix.arch }}-${{ matrix.busybox }} + - name: Test + run: | + test "$(docker run --rm -i "test-${{ matrix.arch }}-${{ matrix.busybox }}" -c "php -r 'echo shell_exec(\"whoami\");'")" = "nonroot" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ac332b9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +ARG _DEB_VERSION="12" + +ARG ARCH="arm64/v8" + +ARG BASE="debian:${_DEB_VERSION}" +ARG BASE_PKGS="php" +ARG BASE_BINS="php" +ARG BASE_PKG_INSTALL_CMD="apt-get update && apt-get install -y" + +ARG BUSYBOX="busybox:latest" + +ARG TARGET="gcr.io/distroless/base-nossl-debian${_DEB_VERSION}:latest" + +FROM --platform="linux/${ARCH}" ${BUSYBOX} AS busybox + +FROM --platform="linux/${ARCH}" ${BASE} AS base + +ARG BASE_PKGS +ARG BASE_BINS +ARG BASE_PKG_INSTALL_CMD + +COPY --chmod=755 "dependency_resolve" "/usr/local/bin/dependency_resolve" + +RUN /bin/sh -c "${BASE_PKG_INSTALL_CMD} ${BASE_PKGS}" \ + && /usr/local/bin/dependency_resolve \ + "$(which "ldd")" \ + $(echo "${BASE_BINS}" | xargs which) \ + | xargs -I {} sh -c 'mkdir -p /root/rootfs/$(dirname "{}") && cp -apP "{}" "/root/rootfs/{}"' + +FROM --platform="linux/${ARCH}" ${TARGET} AS target + +ARG BASE_BINS + +COPY --from=base "/root/rootfs" "/" + +COPY --from=busybox "/bin/busybox" "/bin/busybox" +RUN ["/bin/busybox", "--install", "-s"] + +USER nonroot + +ENTRYPOINT ["/bin/sh"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..330ec02 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# dependency_resolve - distroless packaging support + +Binary packaging support tool for distroless / alpine. + +## Usage + +```Dockerfile +FROM debian:12 AS builder +COPY --chmod=755 "dependency_resolve" "/usr/local/bin/dependency_resolve" +RUN apt-get update && apt-get install -y "php" +RUN dependency_resolve "$(which "ldd")" "$(which "php")" | xargs -I {} sh -c 'mkdir -p /root/rootfs/$(dirname "{}") && cp -apP "{}" "/root/rootfs/{}"' + +FROM gcr.io/distroless/base-nossl-debian12:latest +COPY --from=builder "/root/rootfs" "/" + +ENTRYPOINT ["/usr/bin/php"] +``` + +See `Dockerfile` for more details. diff --git a/dependency_resolve b/dependency_resolve new file mode 100755 index 0000000..1513275 --- /dev/null +++ b/dependency_resolve @@ -0,0 +1,115 @@ +#!/bin/sh + +DRSLV_VERSION="2.0.0" + +DRSLV_SUCCESS=0 +DRSLV_ERROR_WRONG_PARAMS=1 +DRSLV_ERROR_PROCESS_FAILED=2 +DRSLV_ERROR_LDD_NOT_FOUND=3 +DRSLV_ERROR_LDD_NOT_SUPPORTED=4 +DRSLV_ERROR_BINARY_NOT_FOUND=5 + +usage() { + echo "dependency resolve - distroless packaging support v${DRSLV_VERSION} + +usage: ${0} [ldd_binary_path] ...[target_binary_paths] +" +} + +version() { + echo "${DRSLV_VERSION}" +} + +check_ldd() { + if [ ! -f "${1}" ]; then + echo "ldd not found: ${1}" >&2 + exit ${DRSLV_ERROR_LDD_NOT_FOUND} + fi + + ldd_version=$("${1}" --version 2>&1) + if ! echo "${ldd_version}" | grep -qE '(GLIBC|musl libc)'; then + echo "ldd executable not supported: ${1}" >&2 + exit ${DRSLV_ERROR_LDD_NOT_SUPPORTED} + fi +} + +check_binary() { + if [ ! -e "${1}" ]; then + echo "binary not found: ${1}" >&2 + exit ${DRSLV_ERROR_BINARY_NOT_FOUND} + fi +} + +resolve_symlink() { + path="${1}" + result="${path}" + while [ -L "${path}" ]; do + link_target=$(readlink "${path}") + if echo "${link_target}" | grep -q '^/'; then + path="${link_target}" + else + path="$(cd "$(dirname "${path}")" && pwd)/${link_target}" + fi + result="${result} ${path}" + done + echo "${result}" +} + +dependency_resolve() { + ldd_path="${1}" + binary_path="${2}" + result="" + + resolved_paths=$(resolve_symlink "${binary_path}") + for path in ${resolved_paths}; do + result="${result} ${path}" + if [ -f "${path}" ]; then + ldd_output=$("${ldd_path}" "${path}" 2>&1) + if ! echo "${ldd_output}" | grep -qE '(not a dynamic executable|Not a valid dynamic program)'; then + while read -r line; do + library=$(echo "${line}" | awk '{print $3}') + if [ -n "${library}" ] && [ "${library}" != "not" ]; then + result="${result} $(dependency_resolve "${ldd_path}" "${library}")" + fi + done << EOF +${ldd_output} +EOF + fi + fi + done + + echo "${result}" +} + +if [ $# -lt 2 ]; then + case "${1}" in + -v|--version) + version + exit ${DRSLV_SUCCESS} + ;; + -h|--help) + usage + exit ${DRSLV_SUCCESS} + ;; + *) + usage + exit ${DRSLV_ERROR_WRONG_PARAMS} + ;; + esac +fi + +ldd_path="${1}" +check_ldd "${ldd_path}" + +shift +for binary_path in "$@"; do + check_binary "${binary_path}" +done + +dependencies="" +for binary_path in "$@"; do + dependencies="${dependencies} $(dependency_resolve "${ldd_path}" "${binary_path}")" +done + +echo "${dependencies}" | tr ' ' '\n' | sort | uniq | sed '/^$/d' +exit ${DRSLV_SUCCESS}