Using remote artifacts without authenticity and integrity checks can lead to the unexpected installation of malicious software in the built container image. In the build environment, where Dockerfiles are compiled into container images, malicious code could gain access to sensitive data, such as build secrets or source code, and durably poison the resulting image. In the runtime environments where the container images are executed, malicious code could access and modify all runtime data and use the container as a pivot to attack the surrounding network environment. By ensuring that a remote artifact is exactly what it is supposed to be before it is used, the environment is protected from unexpected changes before or after it is downloaded. + That is to say if it has been replaced by malware: * on the website where it is published. * in the environment where it is used. Important note: HTTPS protects data in transit from one host to another. It provides authenticity and integrity checks *for the network stream*, not for the downloaded artifact itself. == Ask Yourself Whether * The artifact is a file intended to execute code. * The artifact is a file that is intended to configure or affect running code in some way. There is a risk if you answer yes to any of these questions. == Recommended Secure Coding Practices To check the authenticity and integrity of a remote artifact, `gpg` is the preferred method, except on Windows hosts, since it is not available by default. In this case, *authenticode* is the method of choice. If the remote publisher does not provide a signature for the desired artifact, hash verification is the most reliable solution. While it does not prove who the creator of the artifact is, it does ensure that the file has not been modified since the fingerprint was computed. In this case, the artifact's hash must: * Be computed with a secure hash algorithm such as `SHA512`, `SHA384`, or `SHA256`. * Be compared with a secure hash that was *not* downloaded from the same source. ** To do so, the best option is to compute the hash before and add it in the code explicitly. *Note: Use the hash fix together with version binding. Avoid using tags like "latest" or similar, so that container builds are not corrupted when the version of the remote artifact changes.* == Sensitive Code Example For Linux-based images: [source,docker] ---- FROM debian RUN curl https://example.com/installer.sh | bash # Sensitive RUN curl https://example.com/installer.py -o installer.py; \ python3 installer.py # Sensitive ---- For Windows-based images: [source,docker] ---- FROM mcr.microsoft.com/windows/servercore:ltsc2019 RUN Invoke-Expression ((new-object net.webclient).DownloadString('https://example.com/installer.ps1')) # Sensitive RUN Invoke-WebRequest 'https://example.com/installer.ps1' -OutFile 'installer.ps1' -UseBasicParsing ; \ python script.ps1 # Sensitive ---- For `ADD`-originating artifacts: [source,docker] ---- FROM alpine ADD https://example.com/Makefile / # Sensitive RUN make install ---- == Compliant Solution Note that the compliant solutions can also be used with artifacts originating from an `ADD \https://example.com/artifact` instruction. GPG solution for Linux-based images: [source,docker] ---- FROM debian ENV GPG_KEY_SERVER "hkps://keys.openpgp.org" ENV GPG_KEY_ID "A035C8C19219BA821ECEA86B64E628F8D684696D" RUN set -eux; \ \ wget -O installer.tar.xz "https://example.com/installer.tar.xz"; \ wget -O installer.tar.xz.asc "https://example.com/installer.tar.xz.asc"; \ \ GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; \ gpg --batch --keyserver ${GPG_KEY_SERVER} --recv-keys "${GPG_KEY_ID}"; \ gpg --batch --verify installer.tar.xz.asc installer.tar.xz ---- For Windows-based images, GPG is not available by default, so it needs to be downloaded beforehand. Thus, it is required to be checked before use. To verify `GPG4Win`, use Authenticode. It is recommended to avoid using software to verify itself. Compromised verification software might always validate itself. Here is an example of using GPG to verify a software on Windows: [source,docker] ---- FROM mcr.microsoft.com/windows/servercore:ltsc2019 ENV GPG4WIN_VERSION "2.3.4" ENV GPG4WIN_AUTHENTICODE "DE16D5972F0B7395F7D91EDC1F219B0FFE89FAB3" # Download and verify gpg4win - Using Authenticode RUN Invoke-WebRequest \ -Uri $('http://files.gpg4win.org/gpg4win-vanilla-{}.exe' -f $env:GPG4WIN_VERSION) \ -OutFile 'gpg4win.exe' \ -UseBasicParsing; \ \ $authenticode = Get-AuthenticodeSignature 'gpg4win.exe'; \ if ( $authenticode.Status -ne 'Valid' ) \ { Write-Error 'Authenticode check failed.'; }; \ if ( $authenticode.SignerCertificate.Thumbprint -ne $env:GPG4WIN_AUTHENTICODE ) \ { Write-Error 'Authenticode signer check failed.'; }; \ \ Start-Process .\gpg4win.exe -ArgumentList '/S' -NoNewWindow -Wait ENV SOFTWARE_VERSION "3.10.8" ENV SOFTWARE_AUTHENTICODE_THUMBPRINT "36168EE17C1A240517388540C903BB6717DD2563" ENV SOFTWARE_GPG_KEYSERVER "hkps://keys.openpgp.org" ENV SOFTWARE_GPG_KEY_ID "A035C8C19219BA821ECEA86B64E628F8D684696D" # Download and verify software - Using gpg4win # You can use gpg4win and Authenticode. RUN $url = $('https://example.com/software-${}.exe' -f $env:SOFTWARE_VERSION); \ \ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \ Invoke-WebRequest -Uri $url -OutFile 'software.exe' -UseBasicParsing; \ Invoke-WebRequest -Uri $url.asc -OutFile 'software.exe.asc' -UseBasicParsing; \ \ if ( (Get-AuthenticodeSignature 'software.exe').Status -ne 'Valid' ) \ { Write-Error 'Authenticode check failed.' } ; \ if ( (Get-AuthenticodeSignature "software.exe").SignerCertificate.Thumbprint -ne $env:GPG_SIGNER ) \ { Write-Error 'Authenticode signer check failed.'; }; \ \ gpg --batch --keyserver "${SOFTWARE_GPG_KEYSERVER}" --recv-keys $env:SOFTWARE_GPG_KEY_ID; \ gpg --batch --verify software.exe.asc software.exe; ---- SHA256 solution for Linux-based images: [source,docker] ---- FROM debian ENV ARTIFACT_SHA256 "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" RUN set -eux; \ \ wget -O installer.py "https://example.com/installer.py"; \ echo "$ARTIFACT_SHA256 *installer.py" | sha256sum -c - ---- SHA256 solution for Windows-based images: [source,docker] ---- FROM mcr.microsoft.com/windows/servercore:ltsc2019 ENV SOFTWARE_SHA256 "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" RUN Invoke-WebRequest 'https://example.com/software.exe' -OutFile 'software.exe' -UseBasicParsing ; \ $fileHash = Get-FileHash 'software.exe' -Algorithm sha256; \ if ( $fileHash.Hash -ne $env:SOFTWARE_SHA256 ) \ { Write-Error 'Integrity check failed.'; }; \ Start-Process .\software.exe; ---- SHA256 version on `ADD` instructions, for Dockerfiles using a non-stable syntax: [source,docker] ---- # syntax=docker/dockerfile:1-labs FROM alpine ADD \ --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d \ https://example.com/Makefile / RUN make install ---- == See * https://cwe.mitre.org/data/definitions/384.html[MITRE, CWE-345] - Insufficient Verification of Data Authenticity * https://learn.microsoft.com/en-us/windows-hardware/drivers/install/authenticode[Microsoft, Authenticode Digital Signatures] * https://www.linux.com/training-tutorials/pgp-web-trust-core-concepts-behind-trusted-communication/[Linux.com, PGP Web of Trust: Core Concepts Behind Trusted Communication] ifdef::env-github,rspecator-view[] ''' == Implementation Specification (visible only on this page) === Message * "Standard stream piping" detection: Downloading from this location without authenticity and integrity checks expose the container to network attacks. Make sure it is safe here. * Invoke-Expression: Running code without authenticity and integrity checks expose the container to network attacks. Make sure it is safe here. * File downloaded and used without checks: The authenticity and integrity of this artifact are not checked and expose the container to network attacks. Make sure it is safe here. === Highlighting * "Standard stream piping" detection: The downloading command. * Invoke-Expression: the full instruction * File downloaded and used without checks: The URL endif::env-github,rspecator-view[]