diff --git a/scripts/ci/brotli-safe-composer.sh b/scripts/ci/brotli-safe-composer.sh index dde36efba..7fb4c3c8b 100755 --- a/scripts/ci/brotli-safe-composer.sh +++ b/scripts/ci/brotli-safe-composer.sh @@ -20,7 +20,9 @@ floor='brotli-libs>=1.2.1-r0' # Refuses IMAGE unless its installed brotli-libs meets the floor and every # Brotli decoder its PHP links belongs to that package. check() { - docker run --rm --network none --user 0 --entrypoint sh "$1" -c ' + local checked_image + checked_image=$1 + docker run --rm --network none --user 0 --entrypoint sh "$checked_image" -c ' set -eu apk info -e "$1" > /dev/null || { echo "Composer image lacks $1" >&2; exit 1; } for library in $(ldd "$(command -v php)" | awk "/libbrotlidec/ {print \$3}"); do diff --git a/scripts/ci/run-composer.sh b/scripts/ci/run-composer.sh index 89eded9af..ed48f33d7 100644 --- a/scripts/ci/run-composer.sh +++ b/scripts/ci/run-composer.sh @@ -17,9 +17,13 @@ here=$(dirname "$0") options=() while (($#)) && [[ $1 != -- ]]; do case "$1" in - *COMPOSER_CAFILE* | *COMPOSER_CAPATH* | --entrypoint* | --env-file*) + *COMPOSER_CAFILE* | *COMPOSER_CAPATH* | --entrypoint* | --env-file* | --volumes-from*) echo "run-composer.sh sets the CA pair and the entrypoint itself; refusing $1" >&2 exit 2 ;; + */checked-ca*) + echo "run-composer.sh reserves /checked-ca for its checked CA pair; refusing $1" >&2 + exit 2 ;; + *) ;; esac options+=("$1") shift diff --git a/tests/Unit/Support/composer_trust_test.py b/tests/Unit/Support/composer_trust_test.py index 7177d81d2..7727117de 100644 --- a/tests/Unit/Support/composer_trust_test.py +++ b/tests/Unit/Support/composer_trust_test.py @@ -341,7 +341,15 @@ def wrapper(self, *arguments: str) -> tuple[subprocess.CompletedProcess[str], bo def test_the_caller_cannot_replace_the_ca_pair_or_the_entrypoint(self) -> None: for options in (["-e", "COMPOSER_CAFILE=/etc/ssl/certs/ca-certificates.crt"], ["-eCOMPOSER_CAFILE=/x"], ["--env", "COMPOSER_CAPATH=/etc/ssl/certs"], ["--entrypoint=sh"], ["--entrypoint", "sh"], - ["--env-file", "composer.env"]): + ["--env-file", "composer.env"], ["-v", "/tmp/root.pem:/checked-ca/ca.pem:ro"], + ["--volume=/tmp/trust:/checked-ca:ro"], + ["--mount", "type=bind,src=/tmp/empty,dst=/checked-ca/no-ca-directory"], + ["--mount=type=bind,src=/tmp/empty,dst=/checked-ca/no-ca-directory"], + ["--tmpfs", "/checked-ca"], ["--tmpfs=/checked-ca"], + ["--device", "/dev/null:/checked-ca/ca.pem"], + ["--device=/dev/null:/checked-ca/ca.pem"], + ["--volumes-from", "untrusted-container"], + ["--volumes-from=untrusted-container"]): with self.subTest(options): result, docker_called = self.wrapper("--rm", *options, "--", "install") @@ -355,6 +363,12 @@ def test_composer_arguments_need_the_separator(self) -> None: self.assertEqual(2, result.returncode) self.assertFalse(docker_called) + def test_unrelated_docker_options_reach_the_pinned_image_builder(self) -> None: + result, docker_called = self.wrapper("--rm", "-v", "/tmp:/app", "--", "install") + + self.assertNotEqual(2, result.returncode, result.stderr) + self.assertTrue(docker_called) + def test_the_ca_pair_and_entrypoint_follow_the_callers_options(self) -> None: text = WRAPPER_SCRIPT.read_text() command = text[text.index("docker run --user"):]