[2/2] container_fetcher: Verify that tag and digest match

Message ID 20250625135442.1420977-2-clara.kowalsky@siemens.com
State Superseded, archived
Headers show
Series [1/2] container_fetcher: Fix missing checksum warning | expand

Commit Message

Clara Kowalsky June 25, 2025, 1:54 p.m. UTC
If a tag and digest are specified for a container image in the SRC_URI,
the tag is ignored until now and the container image with the matching
digest is fetched.
With this change, the container image is fetched based on the specified
tag and it is checked whether the digest matches. If not, an error is
thrown.

Signed-off-by: Clara Kowalsky <clara.kowalsky@siemens.com>
---
 meta/lib/container_fetcher.py | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

Comments

Jan Kiszka June 25, 2025, 3:47 p.m. UTC | #1
On 25.06.25 15:54, Clara Kowalsky wrote:
> If a tag and digest are specified for a container image in the SRC_URI,
> the tag is ignored until now and the container image with the matching
> digest is fetched.
> With this change, the container image is fetched based on the specified
> tag and it is checked whether the digest matches. If not, an error is
> thrown.
> 
> Signed-off-by: Clara Kowalsky <clara.kowalsky@siemens.com>
> ---
>  meta/lib/container_fetcher.py | 17 +++++++++++++++++
>  1 file changed, 17 insertions(+)
> 
> diff --git a/meta/lib/container_fetcher.py b/meta/lib/container_fetcher.py
> index 16467abb..75366988 100644
> --- a/meta/lib/container_fetcher.py
> +++ b/meta/lib/container_fetcher.py
> @@ -11,6 +11,7 @@ from   bb.fetch2 import FetchMethod
>  from   bb.fetch2 import logger
>  from   bb.fetch2 import MissingChecksumEvent
>  from   bb.fetch2 import NoChecksumError
> +from   bb.fetch2 import ChecksumError
>  from   bb.fetch2 import runfetchcmd
>  
>  class Container(FetchMethod):
> @@ -47,6 +48,22 @@ class Container(FetchMethod):
>      def download(self, ud, d):
>          tarball = ud.localfile[:-len('.zst')]
>          with tempfile.TemporaryDirectory(dir=d.getVar('DL_DIR')) as tmpdir:
> +            # If both tag and digest are provided, verify they match
> +            if ud.digest and ud.tag != "latest":

Instead of "latest" (which could have been specified explicitly):

and not "tag" in ud.parm

> +                inspect_output = runfetchcmd(f"skopeo inspect docker://{ud.container_name}:{ud.tag}", d, True)
> +                actual_digest = json.loads(inspect_output)["Digest"]
> +                if actual_digest != ud.digest:
> +                    messages = []
> +                    messages.append(f"Checksum mismatch for {ud.container_name}:{ud.tag}")
> +                    messages.append("If this change is expected (e.g. you have upgraded " \
> +                                "to a new version without updating the checksums) " \
> +                                "then you can use these lines within the recipe:")
> +                    messages.append(f'SRC_URI = "docker://{ud.container_name};digest={actual_digest};tag={ud.tag}"')
> +                    messages.append("Otherwise you should retry the download and/or " \
> +                                "check with upstream to determine if the container image has " \
> +                                "become corrupted or otherwise unexpectedly modified.")

Rather long. Is bitbake similarly verbose when detecting a checksum
mismatch?

> +                    raise ChecksumError("\n".join(messages), ud.url, actual_digest)
> +
>              # Take a two steps for downloading into a docker archive because
>              # not all source may have the required Docker schema 2 manifest.
>              runfetchcmd("skopeo copy --preserve-digests " + \

jan
Clara Kowalsky June 25, 2025, 7:58 p.m. UTC | #2
On 25.06.25 17:47, Jan Kiszka wrote:
> On 25.06.25 15:54, Clara Kowalsky wrote:
>> If a tag and digest are specified for a container image in the SRC_URI,
>> the tag is ignored until now and the container image with the matching
>> digest is fetched.
>> With this change, the container image is fetched based on the specified
>> tag and it is checked whether the digest matches. If not, an error is
>> thrown.
>>
>> Signed-off-by: Clara Kowalsky <clara.kowalsky@siemens.com>
>> ---
>>   meta/lib/container_fetcher.py | 17 +++++++++++++++++
>>   1 file changed, 17 insertions(+)
>>
>> diff --git a/meta/lib/container_fetcher.py b/meta/lib/container_fetcher.py
>> index 16467abb..75366988 100644
>> --- a/meta/lib/container_fetcher.py
>> +++ b/meta/lib/container_fetcher.py
>> @@ -11,6 +11,7 @@ from   bb.fetch2 import FetchMethod
>>   from   bb.fetch2 import logger
>>   from   bb.fetch2 import MissingChecksumEvent
>>   from   bb.fetch2 import NoChecksumError
>> +from   bb.fetch2 import ChecksumError
>>   from   bb.fetch2 import runfetchcmd
>>   
>>   class Container(FetchMethod):
>> @@ -47,6 +48,22 @@ class Container(FetchMethod):
>>       def download(self, ud, d):
>>           tarball = ud.localfile[:-len('.zst')]
>>           with tempfile.TemporaryDirectory(dir=d.getVar('DL_DIR')) as tmpdir:
>> +            # If both tag and digest are provided, verify they match
>> +            if ud.digest and ud.tag != "latest":
> 
> Instead of "latest" (which could have been specified explicitly):
> 
> and not "tag" in ud.parm

Ok, I'll change that in v2.

> 
>> +                inspect_output = runfetchcmd(f"skopeo inspect docker://{ud.container_name}:{ud.tag}", d, True)
>> +                actual_digest = json.loads(inspect_output)["Digest"]
>> +                if actual_digest != ud.digest:
>> +                    messages = []
>> +                    messages.append(f"Checksum mismatch for {ud.container_name}:{ud.tag}")
>> +                    messages.append("If this change is expected (e.g. you have upgraded " \
>> +                                "to a new version without updating the checksums) " \
>> +                                "then you can use these lines within the recipe:")
>> +                    messages.append(f'SRC_URI = "docker://{ud.container_name};digest={actual_digest};tag={ud.tag}"')
>> +                    messages.append("Otherwise you should retry the download and/or " \
>> +                                "check with upstream to determine if the container image has " \
>> +                                "become corrupted or otherwise unexpectedly modified.")
> 
> Rather long. Is bitbake similarly verbose when detecting a checksum
> mismatch?
> 
Yes, this is actually exactly the same message bitbake is printing in 
case of checksum mismatch: 
https://github.com/ilbers/isar/blob/master/bitbake/lib/bb/fetch2/__init__.py#L633

BR,
Clara

>> +                    raise ChecksumError("\n".join(messages), ud.url, actual_digest)
>> +
>>               # Take a two steps for downloading into a docker archive because
>>               # not all source may have the required Docker schema 2 manifest.
>>               runfetchcmd("skopeo copy --preserve-digests " + \
> 
> jan
>

Patch

diff --git a/meta/lib/container_fetcher.py b/meta/lib/container_fetcher.py
index 16467abb..75366988 100644
--- a/meta/lib/container_fetcher.py
+++ b/meta/lib/container_fetcher.py
@@ -11,6 +11,7 @@  from   bb.fetch2 import FetchMethod
 from   bb.fetch2 import logger
 from   bb.fetch2 import MissingChecksumEvent
 from   bb.fetch2 import NoChecksumError
+from   bb.fetch2 import ChecksumError
 from   bb.fetch2 import runfetchcmd
 
 class Container(FetchMethod):
@@ -47,6 +48,22 @@  class Container(FetchMethod):
     def download(self, ud, d):
         tarball = ud.localfile[:-len('.zst')]
         with tempfile.TemporaryDirectory(dir=d.getVar('DL_DIR')) as tmpdir:
+            # If both tag and digest are provided, verify they match
+            if ud.digest and ud.tag != "latest":
+                inspect_output = runfetchcmd(f"skopeo inspect docker://{ud.container_name}:{ud.tag}", d, True)
+                actual_digest = json.loads(inspect_output)["Digest"]
+                if actual_digest != ud.digest:
+                    messages = []
+                    messages.append(f"Checksum mismatch for {ud.container_name}:{ud.tag}")
+                    messages.append("If this change is expected (e.g. you have upgraded " \
+                                "to a new version without updating the checksums) " \
+                                "then you can use these lines within the recipe:")
+                    messages.append(f'SRC_URI = "docker://{ud.container_name};digest={actual_digest};tag={ud.tag}"')
+                    messages.append("Otherwise you should retry the download and/or " \
+                                "check with upstream to determine if the container image has " \
+                                "become corrupted or otherwise unexpectedly modified.")
+                    raise ChecksumError("\n".join(messages), ud.url, actual_digest)
+
             # Take a two steps for downloading into a docker archive because
             # not all source may have the required Docker schema 2 manifest.
             runfetchcmd("skopeo copy --preserve-digests " + \