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[]

