owned this note
owned this note
Published
Linked with GitHub
# Notation Offline Signing
###### tags: `notary`
## Overview
By default, the `notation` CLI signs an artifact by signing its manifest obtained from the remote registry. Moreover, the `notation` CLI is capable of offline signing as required by the [Notary V2 Requirement](https://github.com/notaryproject/notaryproject/blob/main/requirements.md#goals).
To reduce ambiguity, **offline signing** is defined as signing artifacts without the access to the Internet and to any registries. *Offline signing* is also often called **local signing**. Additionally, offline signing is usually operated on a single host but it is not restricted to a cluster or a pipeline within a private network.
## Signing Local Content
Following the the [signature specification](https://github.com/notaryproject/notaryproject/blob/main/signature-specification.md), the `notation` CLI allows signing arbitrary content by computing its descriptor.
### Signing
To sign a file `manifest.json` of media type `application/vnd.oci.image.index.v1+json`, use the `--local` option to indicate local reference:
```shell
notation sign --local --media-type application/vnd.oci.image.index.v1+json manifest.json
```
If the `--media-type` option does not present, the manifest is assumed to be a docker image manifest of media type `application/vnd.docker.distribution.manifest.v2+json`.
Alternatively, a binary steam from standard input is accepted by passing `-` as the reference:
```shell
notation sign --local \
--media-type application/vnd.oci.image.index.v1+json - < manifest.json
```
By default, the resulted signature is stored in the cache. It is possible to write the signature to any desired path by the `--output` option. For instance, write the signature to `manifest.sig`:
```shell
notation sign --local --output manifest.sig \
--media-type application/vnd.oci.image.index.v1+json manifest.json
```
Signing with time stamping is still available in the offline signing with the `--timestamp` option. Since there is no Internet access, time stamping by *public* TSAs are not available.
### Verification
Similar to the signing process, offline verification can be done using the `--local` option, the `--media-type` option, and the `--signature` option. For instance, verify the manifest file `manifest.json` of a target artifact of media type `application/vnd.oci.image.index.v1+json` against a given signature file `manifest.sig`:
```shell
notation verify --local --signature manifest.sig \
--media-type application/vnd.oci.image.index.v1+json manifest.json
```
If the `--signature` option does not present, the `notation` CLI tries to read signatures from the cache.
## Manifest Source
As mentioned in the above section, the `notation` CLI is able to sign any content, which implies that it is able to sign any artifact by signing its manifest. However, the `notation` CLI *does not know and should not know* how the manifest to be signed is generated. In other words, it is the responsibility of the artifact manufacturer to produce the manifest and then present to the `notation` CLI for signing.
For example, suppose we have the following command,
```shell
notation sign --local registry.wabbit-networks.io/net-monitor:v1
```
the `notation` CLI has no idea about which local provider provides the manifest of the image `registry.wabbit-networks.io/net-monitor:v1`. It could be `dockerd`, `buildkit`, `containerd`, `oras`, or other artifact manufacturers.
One may think about having provider registration in the `notation` CLI like
```shell
notation sign --local dockerd://registry.wabbit-networks.io/net-monitor:v1
```
However, it introduces unnecessary complexities that each artifact manufacturer has to
- Implement `notation` specific artifact provider,
- Register itself in the Notation configuration,
- Be available alongside the `notation` CLI on the same host,
- Not suitable for pipeline scenarios.
- Offload image-specific options to a configuration file.
Therefore, the following process is suggested for *offline signing*:
- Stage 1: Produce a manifest with desired tools for the target artifact.
- The manifest MUST be exactly the same as the one, which will be pushed to the remote registry.
- Stage 2: Pass the manifest in the stage 1 to `notation sign --local`.
It is worth noting that the stage 1 and the stage 2 can be processed on different hosts.
Similarly, the following process is suggested for *offline verification*:
- Stage 1: Produce a manifest with desired tools for the target artifact.
- Stage 2: Pass the manifest in the stage 1 to `notation verify --local`.
### Docker Integration
Docker does not store image manifests on the local disk. Instead, it generates manifests for the images when they are being pushed.
To offline sign a docker image, we need to generate the image manifest in the way exactly the same as the `docker push` command. We can leverage the [docker generate](https://github.com/notaryproject/notation/tree/main/cmd/docker-generate) plugin to generate a docker manifest of media type `application/vnd.docker.distribution.manifest.v2+json` from a *local* docker image,
```shell
docker generate manifest net-monitor:v1 > net-monitor_v1.json
```
In the pure offline scenarios, docker images are transferred via tarballs. The `docker generate` plugin is also capable of generating manifests for images in tar archives:
```bash
# Save the `net-monitor:v1` image to a tarball `net-monitor_v1.tar`
# and transfer it to other hosts via offline physical means.
docker save -o net-monitor_v1.tar net-monitor:v1
# On the other host, generate the manifest from the received tarball.
docker generate manifest < net-monitor_1.tar > net-monitor_v1.json
```
Once we have the manifest file of the docker image, we can do `notation sign` and `notation verify` for offline signing and verification.
### Containerd Integration
`containerd` stores image manifests on the local disk in its content store located at
```
/var/lib/containerd/io.containerd.content.v1.content/blobs/<algorithm>/<hash>
```
To offline sign an image, we need find out the manifest of the image and its media type. The following bash script `get_manifest.sh` finds out the manifest of a target image and its media type, using the `ctr` tool. The media type will be written to the standard output, and the manifest will be copied to `manifest.json` in the working folder.
```bash
#!/bin/bash
name=$1
sudo ctr images check name==$name | (
read column_names
read ref type digest others
if [[ -z $ref ]]; then
echo "$name: not found"
exit 1
fi
echo $type
path="/var/lib/containerd/io.containerd.content.v1.content/blobs/${digest/:/\/}"
cp --no-preserve=mode $path manifest.json
)
```
For instance, offline signs the `registry.wabbit-networks.io/net-monitor:v1` and save the signature in the cache,
```bash
media_type=$(./get_manifest.sh registry.wabbit-networks.io/net-monitor:v1)
notation sign --local --media-type $media_type manifest.json
```
For advanced users, the above two scripts can be combined as a single script `sign.sh` so that no intermediate file is generated.
```bash
#!/bin/bash
name=$1
sudo ctr images check name==$name | (
read column_names
read ref type digest others
if [[ -z $ref ]]; then
echo "$name: not found"
exit 1
fi
path="/var/lib/containerd/io.containerd.content.v1.content/blobs/${digest/:/\/}"
notation sign --local --media-type $type $path
)
```
Similarly, offline verification can be done as
```bash
#!/bin/bash
name=$1
sudo ctr images check name==$name | (
read column_names
read ref type digest others
if [[ -z $ref ]]; then
echo "$name: not found"
exit 1
fi
path="/var/lib/containerd/io.containerd.content.v1.content/blobs/${digest/:/\/}"
notation verify --local --media-type $type $path
)
```
## Open Questions
1. Should the `notation` CLI provide signing or verifying a descriptor directly without presenting the original manifest?
- Note: Notary signature format may specific different hash algorithms for the descriptor.