From 3696f66affd52d8c6d4f3d1c6fa182f0b8196761 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Wed, 7 Feb 2024 09:53:31 +0000 Subject: [PATCH 1/5] Disable credential helper to fix "Failed to extract zip: unzip is not installed" on Windows --- src/CredentialHelper/Manager.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/CredentialHelper/Manager.php b/src/CredentialHelper/Manager.php index d4187bad3..fd4f66e38 100644 --- a/src/CredentialHelper/Manager.php +++ b/src/CredentialHelper/Manager.php @@ -225,7 +225,7 @@ private function extractBinFromArchive($archiveContents, $zip, $internalFilename $command = 'unzip ' . escapeshellarg($tmpFile) . ' -d ' . escapeshellarg($tmpDir); $this->shell->execute($command, null, true); } else { - throw new \RuntimeException('Failed to extract zip: unzip is not installed'); + throw new \RuntimeException('Failed to extract zip: either the PHP zip extension or an unzip command are required.'); } } else { $command = 'tar -xzp -f ' . escapeshellarg($tmpFile) . ' -C ' . escapeshellarg($tmpDir); @@ -291,6 +291,9 @@ private function getHelper() { } if (OsUtil::isWindows()) { + if (!class_exists('\\ZipArchive') && !$this->shell->commandExists('unzip')) { + throw new \RuntimeException('Unable to find a credentials helper for this system (either the PHP zip extension or an unzip command are required)'); + } return $helpers['wincred']; } From db9cde75e917b40dcf5a84dbf1e75bcd94e5fa24 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Wed, 7 Feb 2024 10:09:43 +0000 Subject: [PATCH 2/5] Use new credential helpers (distributed as binaries) --- src/CredentialHelper/Manager.php | 144 +++++++++++++------------------ 1 file changed, 61 insertions(+), 83 deletions(-) diff --git a/src/CredentialHelper/Manager.php b/src/CredentialHelper/Manager.php index fd4f66e38..2b64d2e1b 100644 --- a/src/CredentialHelper/Manager.php +++ b/src/CredentialHelper/Manager.php @@ -171,7 +171,9 @@ private function download(array $helper, $destination) { } // Download from the URL. - $contents = file_get_contents($helper['url'], false, \stream_context_create($this->config->getStreamContextOptions(60))); + $contextOptions = $this->config->getStreamContextOptions(60); + $contextOptions['http']['follow_location'] = 1; + $contents = file_get_contents($helper['url'], false, \stream_context_create($contextOptions)); if (!$contents) { throw new \RuntimeException('Failed to download credentials helper from URL: ' . $helper['url']); } @@ -180,12 +182,16 @@ private function download(array $helper, $destination) { $fs = new Filesystem(); $tmpFile = $fs->tempnam(sys_get_temp_dir(), 'cli-helper-extracted'); try { - // Extract the archive. - $this->extractBinFromArchive($contents, substr($helper['url'], -4) === '.zip', $helper['filename'], $tmpFile); + // Write the file. + $bytes = file_put_contents($tmpFile, $contents); + if (!$bytes) { + throw new \RuntimeException(sprintf('Failed to write downloaded helper file to: %s', $tmpFile)); + } // Verify the file hash. - if (hash_file('sha256', $tmpFile) !== $helper['sha256']) { - throw new \RuntimeException('Failed to verify downloaded file for helper: ' . $helper['url']); + $hash = hash_file('sha256', $tmpFile); + if ($hash !== $helper['sha256']) { + throw new \RuntimeException(sprintf('Failed to verify downloaded file for helper: %s (size: %d, hash: %s, expected: %s)', $helper['url'], $bytes, $hash, $helper['sha256'])); } // Make the file executable and move it into place. @@ -197,73 +203,46 @@ private function download(array $helper, $destination) { } } - /** - * Extracts the internal file from the package and moves it to a destination. - * - * @param string $archiveContents - * @param bool $zip - * @param string $internalFilename - * @param string $destination - */ - private function extractBinFromArchive($archiveContents, $zip, $internalFilename, $destination) { - $fs = new Filesystem(); - $tmpDir = $tmpFile = $fs->tempnam(sys_get_temp_dir(), 'cli-helpers'); - $fs->remove($tmpFile); - $fs->mkdir($tmpDir); - try { - $tmpFile = $fs->tempnam($tmpDir, 'cli-helper'); - if (!file_put_contents($tmpFile, $archiveContents)) { - throw new \RuntimeException('Failed to write credentials helper to file: ' . $tmpFile); - } - if ($zip) { - if (class_exists('\\ZipArchive')) { - $zip = new \ZipArchive(); - if (!$zip->open($tmpFile) || !$zip->extractTo($tmpDir) || !$zip->close()) { - throw new \RuntimeException('Failed to extract zip: ' . ($zip->getStatusString() ?: 'unknown error')); - } - } elseif ($this->shell->commandExists('unzip')) { - $command = 'unzip ' . escapeshellarg($tmpFile) . ' -d ' . escapeshellarg($tmpDir); - $this->shell->execute($command, null, true); - } else { - throw new \RuntimeException('Failed to extract zip: either the PHP zip extension or an unzip command are required.'); - } - } else { - $command = 'tar -xzp -f ' . escapeshellarg($tmpFile) . ' -C ' . escapeshellarg($tmpDir); - $this->shell->execute($command, null, true); - } - if (!file_exists($tmpDir . DIRECTORY_SEPARATOR . $internalFilename)) { - throw new \RuntimeException('File not found: ' . $tmpDir . DIRECTORY_SEPARATOR . $internalFilename); - } - $fs->rename($tmpDir . DIRECTORY_SEPARATOR . $internalFilename, $destination, true); - } finally { - $fs->remove($tmpDir); - } - } - /** * @return array */ private function getHelpers() { return [ - 'wincred' => [ - 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.6.4/docker-credential-wincred-v0.6.4-amd64.zip', - 'filename' => 'docker-credential-wincred.exe', - 'sha256' => 'a4e7885d3d469b2e3c92ab72c729e35df94ed1952bc8090272107fef0652e474', + 'windows' => [ + 'amd64' => [ + 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.8.1/docker-credential-wincred-v0.8.1.windows-amd64.exe', + 'filename' => 'docker-credential-wincred.exe', + 'sha256' => '86c3aa9120ad136e5f7d669267a8e271f0d9ec2879c75908f20a769351043a28', + ], + 'arm64' => [ + 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.8.1/docker-credential-wincred-v0.8.1.windows-arm64.exe', + 'filename' => 'docker-credential-wincred.exe', + 'sha256' => 'a83bafb13c168de1ecae48dbfc5e6f220808be86dd4258dd72fd9bcefdf5a63c', + ], ], - 'secretservice' => [ - 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.6.4/docker-credential-secretservice-v0.6.4-amd64.tar.gz', - 'filename' => 'docker-credential-secretservice', - 'sha256' => '1bddcc1da7ea4d6f50d8e21ba3f0d2ae518c04d90553f543e683af62e9c7c9a8', + 'linux' => [ + 'amd64' => [ + 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.8.1/docker-credential-secretservice-v0.8.1.linux-amd64', + 'filename' => 'docker-credential-secretservice', + 'sha256' => '9a5875bc9435c2c8f9544419c249f866b372b054294f169444f66bb925d96edc', + ], + 'arm64' => [ + 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.8.1/docker-credential-secretservice-v0.8.1.linux-arm64', + 'filename' => 'docker-credential-secretservice', + 'sha256' => '1093ff44716b9d8c3715d0e5322ba453fd1a77faad8b7e1ba3ad159bf6e10887', + ], ], - 'osxkeychain' => [ - 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.6.4/docker-credential-osxkeychain-v0.6.4-amd64.tar.gz', - 'filename' => 'docker-credential-osxkeychain', - 'sha256' => '76c4088359bbbcd25b8d0ff8436086742b6184aba6380ae57d39e5513f723b74', - ], - 'osxkeychain-arm64' => [ - 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.6.4/docker-credential-osxkeychain-v0.6.4-arm64.tar.gz', - 'filename' => 'docker-credential-osxkeychain', - 'sha256' => '902e8237747aac0eca61efa1875e65aa8552b7c95fc406cf0d2aef733dda41de', + 'darwin' => [ + 'amd64' => [ + 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.8.1/docker-credential-osxkeychain-v0.8.1.darwin-amd64', + 'filename' => 'docker-credential-osxkeychain', + 'sha256' => '7acd433a8ab95c3180ef740ce30aa3d21d2877f4ceb35de797e4eb595168e3c8', + ], + 'arm64' => [ + 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.8.1/docker-credential-osxkeychain-v0.8.1.darwin-arm64', + 'filename' => 'docker-credential-osxkeychain', + 'sha256' => '0db0f8e7e3db93a720da55760bbe26e1266648515b8a0a9539185a5503d03449', + ], ], ]; } @@ -275,29 +254,30 @@ private function getHelpers() { */ private function getHelper() { $arch = php_uname('m'); + if ($arch === 'ARM64') { + $arch = 'arm64'; + } elseif (\in_array($arch, ['x86_64', 'amd64', 'AMD64'])) { + $arch = 'amd64'; + } $helpers = $this->getHelpers(); - if (OsUtil::isOsX()) { - if ($arch === 'arm64') { - return $helpers['osxkeychain-arm64']; - } elseif (\in_array($arch, ['x86_64', 'amd64', 'AMD64'])) { - return $helpers['osxkeychain']; - } + if (OsUtil::isWindows()) { + $os = 'windows'; + } elseif (OsUtil::isLinux()) { + $os = 'linux'; + } elseif (OsUtil::isOsX()) { + $os = 'darwin'; } - if (!in_array($arch, ['x86_64', 'amd64', 'AMD64'])) { - throw new \RuntimeException('Unable to find a credentials helper for this system architecture'); + if (!isset($os) || !isset($helpers[$os])) { + throw new \RuntimeException('Unable to find a credentials helper for this operating system'); } - - if (OsUtil::isWindows()) { - if (!class_exists('\\ZipArchive') && !$this->shell->commandExists('unzip')) { - throw new \RuntimeException('Unable to find a credentials helper for this system (either the PHP zip extension or an unzip command are required)'); - } - return $helpers['wincred']; + if (!isset($helpers[$os][$arch])) { + throw new \RuntimeException(sprintf('Unable to find a credentials helper for this operating system (%s) and architecture (%s)', $os, $arch)); } - if (OsUtil::isLinux()) { + if ($os === 'linux') { // The Linux helper probably needs an X display. if (in_array(getenv('DISPLAY'), [false, 'none'], true)) { throw new \RuntimeException('Unable to find a credentials helper for this system (no DISPLAY)'); @@ -318,11 +298,9 @@ private function getHelper() { if (!$this->shell->execute('ldconfig --print-cache | grep -q libsecret')) { throw new \RuntimeException('Unable to find a credentials helper for this system (libsecret is not installed)'); } - - return $helpers['secretservice']; } - throw new \RuntimeException('Unable to find a credentials helper for this system'); + return $helpers[$os][$arch]; } /** From 624d0624bdcf37a3c22aa573da0a9b55d1ae4d13 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Wed, 7 Feb 2024 10:24:20 +0000 Subject: [PATCH 3/5] Cleanup --- src/CredentialHelper/Manager.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/CredentialHelper/Manager.php b/src/CredentialHelper/Manager.php index 2b64d2e1b..7895dffb5 100644 --- a/src/CredentialHelper/Manager.php +++ b/src/CredentialHelper/Manager.php @@ -178,15 +178,13 @@ private function download(array $helper, $destination) { throw new \RuntimeException('Failed to download credentials helper from URL: ' . $helper['url']); } - // Work in a temporary file. $fs = new Filesystem(); - $tmpFile = $fs->tempnam(sys_get_temp_dir(), 'cli-helper-extracted'); + + // Work in a temporary file. + $tmpFile = $destination . '-tmp'; try { // Write the file. - $bytes = file_put_contents($tmpFile, $contents); - if (!$bytes) { - throw new \RuntimeException(sprintf('Failed to write downloaded helper file to: %s', $tmpFile)); - } + $fs->dumpFile($tmpFile, $contents); // Verify the file hash. $hash = hash_file('sha256', $tmpFile); @@ -196,7 +194,6 @@ private function download(array $helper, $destination) { // Make the file executable and move it into place. $fs->chmod($tmpFile, 0700); - $fs->mkdir(dirname($destination), 0700); $fs->rename($tmpFile, $destination, true); } finally { $fs->remove($tmpFile); From d46baef752ebb190b12917e0fe9bfc7cfe67f3e4 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Fri, 9 Feb 2024 14:51:20 +0000 Subject: [PATCH 4/5] Revert the OS X version to 0.6.4 --- src/CredentialHelper/Manager.php | 66 ++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/src/CredentialHelper/Manager.php b/src/CredentialHelper/Manager.php index 7895dffb5..85e886da4 100644 --- a/src/CredentialHelper/Manager.php +++ b/src/CredentialHelper/Manager.php @@ -183,13 +183,17 @@ private function download(array $helper, $destination) { // Work in a temporary file. $tmpFile = $destination . '-tmp'; try { - // Write the file. - $fs->dumpFile($tmpFile, $contents); + // Write the file, either directly or via extracting its archive. + if (preg_match('/(\.tar\.gz|\.tgz|\.zip)$/', $helper['url']) === 1) { + $this->extractBinFromArchive($contents, substr($helper['url'], -4) === '.zip', $helper['filename'], $tmpFile); + } else { + $fs->dumpFile($tmpFile, $contents); + } // Verify the file hash. $hash = hash_file('sha256', $tmpFile); if ($hash !== $helper['sha256']) { - throw new \RuntimeException(sprintf('Failed to verify downloaded file for helper: %s (size: %d, hash: %s, expected: %s)', $helper['url'], $bytes, $hash, $helper['sha256'])); + throw new \RuntimeException(sprintf('Failed to verify downloaded file for helper: %s (hash: %s, expected: %s)', $helper['url'], $hash, $helper['sha256'])); } // Make the file executable and move it into place. @@ -200,6 +204,50 @@ private function download(array $helper, $destination) { } } + /** + * Extracts the internal file from a package archive and moves it to a destination. + * + * @param string $archiveContents + * @param bool $zip + * @param string $internalFilename + * @param string $destination + */ + private function extractBinFromArchive($archiveContents, $zip, $internalFilename, $destination) + { + $fs = new Filesystem(); + $tmpDir = $tmpFile = $fs->tempnam(sys_get_temp_dir(), 'cli-helpers'); + $fs->remove($tmpFile); + $fs->mkdir($tmpDir); + try { + $tmpFile = $fs->tempnam($tmpDir, 'cli-helper'); + if (!file_put_contents($tmpFile, $archiveContents)) { + throw new \RuntimeException('Failed to write credentials helper to file: ' . $tmpFile); + } + if ($zip) { + if (class_exists('\\ZipArchive')) { + $zip = new \ZipArchive(); + if (!$zip->open($tmpFile) || !$zip->extractTo($tmpDir) || !$zip->close()) { + throw new \RuntimeException('Failed to extract zip: ' . ($zip->getStatusString() ?: 'unknown error')); + } + } elseif ($this->shell->commandExists('unzip')) { + $command = 'unzip ' . escapeshellarg($tmpFile) . ' -d ' . escapeshellarg($tmpDir); + $this->shell->execute($command, null, true); + } else { + throw new \RuntimeException('Failed to extract zip: unzip is not installed'); + } + } else { + $command = 'tar -xzp -f ' . escapeshellarg($tmpFile) . ' -C ' . escapeshellarg($tmpDir); + $this->shell->execute($command, null, true); + } + if (!file_exists($tmpDir . DIRECTORY_SEPARATOR . $internalFilename)) { + throw new \RuntimeException('File not found: ' . $tmpDir . DIRECTORY_SEPARATOR . $internalFilename); + } + $fs->rename($tmpDir . DIRECTORY_SEPARATOR . $internalFilename, $destination, true); + } finally { + $fs->remove($tmpDir); + } + } + /** * @return array */ @@ -231,14 +279,14 @@ private function getHelpers() { ], 'darwin' => [ 'amd64' => [ - 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.8.1/docker-credential-osxkeychain-v0.8.1.darwin-amd64', + 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.6.4/docker-credential-osxkeychain-v0.6.4-amd64.tar.gz', 'filename' => 'docker-credential-osxkeychain', - 'sha256' => '7acd433a8ab95c3180ef740ce30aa3d21d2877f4ceb35de797e4eb595168e3c8', + 'sha256' => '76c4088359bbbcd25b8d0ff8436086742b6184aba6380ae57d39e5513f723b74', ], 'arm64' => [ - 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.8.1/docker-credential-osxkeychain-v0.8.1.darwin-arm64', + 'url' => 'https://github.com/docker/docker-credential-helpers/releases/download/v0.6.4/docker-credential-osxkeychain-v0.6.4-arm64.tar.gz', 'filename' => 'docker-credential-osxkeychain', - 'sha256' => '0db0f8e7e3db93a720da55760bbe26e1266648515b8a0a9539185a5503d03449', + 'sha256' => '902e8237747aac0eca61efa1875e65aa8552b7c95fc406cf0d2aef733dda41de', ], ], ]; @@ -297,6 +345,10 @@ private function getHelper() { } } + if (substr($helpers[$os][$arch]['url'], -4) === 'zip' && !class_exists('\\ZipArchive') && !$this->shell->commandExists('unzip')) { + throw new \RuntimeException('Unable to install a credentials helper for this system (it is a .zip file and the zip extension is unavailable)'); + } + return $helpers[$os][$arch]; } From bd38d6afa76989f1eb089d7592bccc2a72a7ef46 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Fri, 16 Feb 2024 11:37:40 +0000 Subject: [PATCH 5/5] Fix zip match --- src/CredentialHelper/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CredentialHelper/Manager.php b/src/CredentialHelper/Manager.php index 85e886da4..dd3782aa3 100644 --- a/src/CredentialHelper/Manager.php +++ b/src/CredentialHelper/Manager.php @@ -345,7 +345,7 @@ private function getHelper() { } } - if (substr($helpers[$os][$arch]['url'], -4) === 'zip' && !class_exists('\\ZipArchive') && !$this->shell->commandExists('unzip')) { + if (substr($helpers[$os][$arch]['url'], -4) === '.zip' && !class_exists('\\ZipArchive') && !$this->shell->commandExists('unzip')) { throw new \RuntimeException('Unable to install a credentials helper for this system (it is a .zip file and the zip extension is unavailable)'); }