Skip to content

Commit

Permalink
Proposal for generic reverse lookup
Browse files Browse the repository at this point in the history
Signed-off-by: Aviral Takkar <avtakkar@microsoft.com>

Updated references to net-monitor

Signed-off-by: Steve Lasker <stevenlasker@hotmail.com>

General improvements

Signed-off-by: Aviral Takkar <avtakkar@microsoft.com>
  • Loading branch information
avtakkar committed Sep 4, 2020
1 parent f4765d1 commit 24001f8
Showing 1 changed file with 368 additions and 0 deletions.
368 changes: 368 additions & 0 deletions docs/distribution/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,368 @@
# OCI Distribution

There is an ongoing discussion to modify OCI index objects to include a config property (see [proposal](https://github.com/notaryproject/nv2/pull/10)). Such a property implies a relationship between the manifests referenced in the index and its config object. For a referenced manifest, one can create a reverse lookup to the index config. With this ability, manifest signatures that are pushed to a registry as index config objects can be stored and retrieved as manifest referrer metadata.

## Table of contents

1. [Manifest Referrer](#manifest-referrer)
2. [Storing Signatures](#storing-signatures)
3. [Retrieving Signatures](#retrieving-signatures)
4. [Implementation](#implementation)
5. [Prototype](#prototype)

## Manifest Referrer

A manifest referrer is any registry artifact that has an immutatable reference to a manifest. An OCI index is a referrer to each manifest it references. Currently, the OCI image spec does not include a config property for an OCI index and there is no reverse lookup of referrers in docker distribution.\
A modified OCI index with a config property that references a collection of manifests allows us to associate a "type" to the referrer-referenced relationship, where the type is the `mediaType` of the index config object, such as `application/vnd.cncf.notary.config.v2+jwt`.

## Storing Signatures

The proposal is to implement a referrer metadata store for manifests that is essentially a reverse-lookup, by `mediaType`, to referrer config objects. For example, when an OCI index is pushed, if it references a config object of media type `application/vnd.cncf.notary.config.v2+jwt`, a link to the config object is recorded in the referrer metadata store of each referenced manifest.

> See [Artifacts submitted to a registry](https://github.com/notaryproject/nv2/blob/276abe450feec8f3b54f0774b7dfb47f36670cc5/docs/distribution/README.md#artifacts-submitted-to-a-registry) for each digest reference used in this example.
### Put an OCI index by digest, linking a signature to a collection of manifests

### Request

`PUT https://localhost:5000/v2/net-monitor/manifests/sha256:222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i`

```json
{
"schemaVersion": 2.1,
"mediaType": "application/vnd.oci.image.index.v3+json",
"config": {
"mediaType": "application/vnd.cncf.notary.config.v2+jwt",
"digest": "sha256:222cb130c152895905abe66279dd9feaa68091ba55619f5b900f2ebed38b222c",
"size": 1906
},
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m",
"size": 7023,
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
}
]
}
```

PUT index would result in the creation of a link between the index config object `sha256:222cb130c152895905abe66279dd9feaa68091ba55619f5b900f2ebed38b222c`and the `net-monitor` manifest `sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m`, of type `application/vnd.cncf.notary.config.v2+jwt`.

## Retrieving Signatures

Signatures are just referrer metadata to a registry. Referrer metdata for a manifest can be retrieved filtered by their types.

### Get a list of paginated signatures for a manifest by tag

### Request

`GET http://localhost:5000/v2/net-monitor/manifests/v1.0/referrer-metadata?media-type=application/vnd.cncf.notary.config.v2+jwt`

> note: the mediaType filter will move to a request header
### Response

The request will return the two signatures (**wabbit-networks** & **acme-rockets**)

> note: the response should be a complete oci-descriptor for each result and root reference
```json
{
"tag": "v1.0",
"@nextLink": "{opaqueUrl}",
"referrerMetadata": [
"sha256:222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i",
"sha256:333ic0c33ebc4a74a0a554c86ac2b28ddf3454a5ad9cf90ea8cea9f9e75c333i"
]
}
```

### Get a list of paginated signatures for a manifest by digest

### Request

`GET http://localhost:5000/v2/net-monitor/manifests/sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m/referrer-metadata?media-type=application/vnd.cncf.notary.config.v2+jwt`

> note: the mediaType filter will move to a request header
### Response

```json
{
"digest": "sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m",
"@nextLink": "{opaqueUrl}",
"referrerMetadata": [
"sha256:222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i",
"sha256:333ic0c33ebc4a74a0a554c86ac2b28ddf3454a5ad9cf90ea8cea9f9e75c333i"
]
}
```

## Implementation

Let's consider an example implementation for docker distribution, backed by file storage. Say that an image already exists in the registry:

- repository: `net-monitor`
- digest: `sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m`
- tag: `v1.0`

The storage layout we're most concerned with at the moment is the repository store where the manifest link file exists. It's shown below:

```bash
<root>
└── v2
└── repositories
└── net-monitor
└── _manifests
└── revisions
└── sha256
└── 111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m
└── link
```

Now we push a signature blob and an OCI index that contains a config property referencing it:

- index digest: `sha256:222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i`
- index json:
```json
{
"schemaVersion": 2.1,
"mediaType": "application/vnd.oci.image.index.v3+json",
"config": {
"mediaType": "application/vnd.cncf.notary.config.v2+jwt",
"digest": "sha256:222cb130c152895905abe66279dd9feaa68091ba55619f5b900f2ebed38b222c",
"size": 1906
},
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m",
"size": 7023,
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
}
]
}
```

On `PUT` index to the repository `net-monitor`, the index appears as a manifest revision as usual. Additionally, a link is added to the referrer metadata store of the manifest. The manifests storage layout would look as follows:

```
<root>
└── v2
└── repositories
└── net-monitor
└── _manifests
└── revisions
└── sha256
├── 111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m
│ ├── link
│ └── referrerMetadata
│ └── application/vnd.cncf.notary.config.v2+jwt
│ └── sha256
│ └── 222cb130c152895905abe66279dd9feaa68091ba55619f5b900f2ebed38b222c
│ └── link
└── 222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i
└── link
```
Let's add another signature for the manifest `sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m`:
- index digest: `sha256:333ic0c33ebc4a74a0a554c86ac2b28ddf3454a5ad9cf90ea8cea9f9e75c333i`
- index json:
```json
{
"schemaVersion": 2.1,
"mediaType": "application/vnd.oci.image.index.v3+json",
"config": {
"mediaType": "application/vnd.cncf.notary.config.v2+jwt",
"digest": "sha256:333cc44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b785c333c",
"size": 1906
},
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m",
"size": 7023,
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
}
]
}
```

The manifest storage layout would look as follows on `PUT` index:

```
<root>
└── v2
└── repositories
└── net-monitor
└── _manifests
└── revisions
└── sha256
├── 111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m
│ ├── link
│ └── referrerMetadata
│ └── application/vnd.cncf.notary.config.v2+jwt
│ └── sha256
│ ├── 222cb130c152895905abe66279dd9feaa68091ba55619f5b900f2ebed38b222c
│ │ └── link
│ └── 333cc44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b785c333c
│ └── link
├── 222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i
│ └── link
└── 333ic0c33ebc4a74a0a554c86ac2b28ddf3454a5ad9cf90ea8cea9f9e75c333i
└── link
```

## Prototype

Available here: https://github.com/notaryproject/distribution/tree/prototype-1

The following steps illustrate how signatures can be stored and retrieved from a registry.

### Prerequisites

- Local registry prototype instance
- [docker-generate](https://github.com/shizhMSFT/docker-generate)
- [nv2](https://github.com/notaryproject/nv2)
- `curl`
- `jq`
- `python3`

### Push an image to your registry

```shell
# Local registry
regIp="127.0.0.1" && \
regPort="5000" && \
registry="$regIp:$regPort" && \
repo="busybox" && \
tag="latest" && \
image="$repo:$tag" && \
reference="$registry/$image"

# Pull image from docker hub and push to local registry
docker pull $image && \
docker tag $image $reference && \
docker push $reference
```

### Generate image manifest and sign it

```shell
# Generate self-signed certificates
openssl req \
-x509 \
-sha256 \
-nodes \
-newkey rsa:2048 \
-days 365 \
-subj "/CN=$regIp/O=example inc/C=IN/ST=Haryana/L=Gurgaon" \
-addext "subjectAltName=IP:$regIp" \
-keyout example.key \
-out example.crt

# Generate image manifest
manifestFile="manifest-to-sign.json" && \
docker generate manifest $image > $manifestFile

# Sign manifest
signatureFile="manifest-signature.jwt" && \
nv2 sign --method x509 \
-k example.key \
-c example.crt \
-r $reference \
-o $signatureFile \
file:$manifestFile
```

### Obtain manifest and signature digests

```shell
manifestDigest="sha256:$(sha256sum $manifestFile | cut -d " " -f 1)" && \
signatureDigest="sha256:$(sha256sum $signatureFile | cut -d " " -f 1)"
```

### Create an OCI index file referencing the manifest that was signed and its signature as config

```shell
indexFile="index.json" && \
indexMediaType="application/vnd.oci.image.index.v3+json" && \
configMediaType="application/vnd.cncf.notary.config.v2+jwt" && \
signatureFileSize=`wc -c < $signatureFile` && \
manifestMediaType="$(cat $manifestFile | jq -r '.mediaType')" && \
manifestFileSize=`wc -c < $manifestFile`

cat <<EOF > $indexFile
{
"schemaVersion": 2,
"mediaType": "$indexMediaType",
"config": {
"mediaType": "$configMediaType",
"digest": "$signatureDigest",
"size": $signatureFileSize
},
"manifests": [
{
"mediaType": "$manifestMediaType",
"digest": "$manifestDigest",
"size": $manifestFileSize
}
]
}
EOF
```

### Obtain index digest

```shell
indexDigest="sha256:$(sha256sum $indexFile | cut -d " " -f 1)"
```

### Push signature and index

```shell
# Initiate blob upload and obtain PUT location
configPutLocation=`curl -I -X POST -s http://$registry/v2/$repo/blobs/uploads/ | grep "Location: " | sed -e "s/Location: //;s/$/\&digest=$signatureDigest/;s/\r//"`

# Push signature blob
curl -X PUT -H "Content-Type: application/octet-stream" --data-binary @"$signatureFile" $configPutLocation

# Push index
curl -X PUT --data-binary @"$indexFile" -H "Content-Type: $indexMediaType" "http://$registry/v2/$repo/manifests/$indexDigest"
```

### Retrieve signatures of a manifest as referrer metadata

```shell
# URL encode index config media type
metadataMediaType=`python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" $configMediaType`

# Retrieve referrer metadata
curl -s "http://$registry/v2/$repo/manifests/$manifestDigest/referrer-metadata?media-type=$metadataMediaType" | jq
```

### Verify signature

```shell
# Retrieve first signature and store it locally
metadataDigest=`curl -s "http://$registry/v2/$repo/manifests/$manifestDigest/referrer-metadata?media-type=$metadataMediaType" | jq -r '.referrerMetadata[0]'` && \
retrievedMetadataFile="retrieved-signature.jwt" && \
curl -s http://$registry/v2/$repo/blobs/$metadataDigest > $retrievedMetadataFile

# Verify signature
nv2 verify \
-f $retrievedMetadataFile \
-c example.crt \
file:$manifestFile
```

0 comments on commit 24001f8

Please sign in to comment.