Skip to content

Enable Image Signing #1376

Open
Open
@lbussell

Description

@lbussell

Overview

This issue will cover the implementation details to enable dotnet/dotnet-docker#4589

I have created a "vertical slice" of the signing infrastructure to verify that everything works. This is in the form of a pipeline that takes a pre-built image, signs it using our signing service, and verifies the signature. The pipeline code is here [internal MSFT link].

How images are signed

  1. Build the Images
  2. Generate Signing Payloads: Once images are built, generate a signing payload for each manifest and manifest list we intend to sign. The payload contains the OCI descriptor for the image to be signed. The descriptor can be retrieved by running the ORAS CLI.
  3. Sign Payloads: Send all of the payloads off to the signing service at once. Our signing service will update the payloads in-place with the signed versions.
  4. Attach Signatures: Next, the signed payload must be attached to the image as an artifact. This is done with the ORAS CLI.
    1. Using the signed payload, read the COSE signature to compute the x509 certificate chain. This is added to the signature artifact as an annotation. There is a reference implementation for this here in python, that we'll need to port over to .NET.

How images are verified

  1. Download the root and/or issuer certificates corresponding to the signing key used for the images. All official and test certificates are accessible from public download links.
  2. Add the certs to the Notation CLI in a new trust store.
  3. Define and import a Notation Trust Policy that trusts all of the certs in the aforementioned trust store.
  4. Then, you can verify the image with the notation CLI.

Design Proposal

Prerequisites:

Pipeline Changes

Signing must take place after all images are built. We'll need a list of all of the digests we built in order to know what to sign. Our usual image-info.json file is a natural fit. That means signing will need to wait until after the Post-Build stage, which assembles the image info file describing all of the images we built. Furthermore, if we wish to sign manifest lists, we'll need to wait until after the Publish Manifest stage (not the most descriptive name), which creates and pushes all of the docker manifest lists for multi-platform tags. It also seems like we could consider moving the manifest list creation into the post-build stage as well.

The pipeline changes should allow signing with either test keys or production keys, and also the provide option to skip signing altogether.

ImageBuilder Changes

Since we need to send signing payloads off to an external service via a pipeline task, the signing implementation in ImageBuilder must be split into at least two separate parts: before and after sending signing payloads to the signing service.

New Command: GenerateSigningPayloads

  • Inputs: image-info.json file, output directory for signing payloads.
  • Outputs: places one signing payload per image in the output directory. We may also find it useful to output an info file mapping each digest to its payload file's path, which we could pass to the next stage.

New Command: AttachSignatures

  • Inputs: One of either (path to directory containing signing payloads) or (output file from GenerateSigningPayloads command)
  • Outputs: List of signature digests.

This will be similar to ApplyEolDigestAnnotations from the current image lifecycle annotation work. There is opportunity to share parts of the implementation here.

In the case of failure, the command should output a list of digests and/or payloads that did not get their signature attached. We should also investigate what happens when there are multiple attempts to attach signatures to the same digest. There should not be two signatures referring to the same digest. One solution could be to remove any old signature annotations and re-attach newly created annotations on a re-run of the pipeline.

Verifying signatures

This is relatively straightforward - this could be done during our test leg, or immediately after attaching signatures. Notation CLI and ORAS only interact with registries and not the local Docker installation. This means verifying signatures does not require pulling any images, so this should be a relatively lightweight process. Performing this check outside of ordinary test infrastructure also means that this check could run on all repos without any test infrastructure changes.

Unknowns

Does ACR import between ACRs leave artifacts and referrers intact?

We may consider using ORAS to move images between ACRs instead of acr import, to keep signatures intact.

Effect on our ACR and clean-up schedule

In order to maintain integrity of the verified content, signatures exist in the registry just like an image, and simply refer to the digest of the image to be verified. Since signatures are stored in the registry just like other artifacts, they also may need to be cleaned up (needs more investigation) along with the images they refer to. In our cleanup pipelines, we may consider using ORAS to check for and delete associated artifacts when cleaning up old images in our ACR.

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Current Release

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions