forked from TrueCloudLab/distribution
Merge pull request #4023 from DavidSpek/remove-schema1-manifest
feat!: remove schema1 manifest and config options
This commit is contained in:
commit
df16076366
14 changed files with 6 additions and 1484 deletions
|
@ -174,30 +174,6 @@ type Configuration struct {
|
|||
|
||||
Proxy Proxy `yaml:"proxy,omitempty"`
|
||||
|
||||
// Compatibility is used for configurations of working with older or deprecated features.
|
||||
Compatibility struct {
|
||||
// Schema1 configures how schema1 manifests will be handled.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since
|
||||
// 2015. These options should only be used if you need to provide
|
||||
// backward compatibility.
|
||||
Schema1 struct {
|
||||
// TrustKey is the signing key to use for adding the signature to
|
||||
// schema1 manifests.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since
|
||||
// 2015. These options should only be used if you need to provide
|
||||
// backward compatibility.
|
||||
TrustKey string `yaml:"signingkeyfile,omitempty"`
|
||||
// Enabled determines if schema1 manifests should be pullable.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since
|
||||
// 2015. These options should only be used if you need to provide
|
||||
// backward compatibility.
|
||||
Enabled bool `yaml:"enabled,omitempty"`
|
||||
} `yaml:"schema1,omitempty"`
|
||||
} `yaml:"compatibility,omitempty"`
|
||||
|
||||
// Validation configures validation options for the registry.
|
||||
Validation struct {
|
||||
// Enabled enables the other options in this section. This field is
|
||||
|
|
|
@ -276,10 +276,6 @@ proxy:
|
|||
username: [username]
|
||||
password: [password]
|
||||
ttl: 168h
|
||||
compatibility:
|
||||
schema1:
|
||||
signingkeyfile: /etc/registry/key.json
|
||||
enabled: true
|
||||
validation:
|
||||
manifests:
|
||||
urls:
|
||||
|
@ -1112,25 +1108,6 @@ username (such as `batman`) and the password for that username.
|
|||
> **Note**: These private repositories are stored in the proxy cache's storage.
|
||||
> Take appropriate measures to protect access to the proxy cache.
|
||||
|
||||
## `compatibility`
|
||||
|
||||
```none
|
||||
compatibility:
|
||||
schema1:
|
||||
signingkeyfile: /etc/registry/key.json
|
||||
enabled: true
|
||||
```
|
||||
|
||||
Use the `compatibility` structure to configure handling of older and deprecated
|
||||
features. Each subsection defines such a feature with configurable behavior.
|
||||
|
||||
### `schema1`
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|-----------|----------|-------------------------------------------------------|
|
||||
| `signingkeyfile` | no | The signing private key used to add signatures to `schema1` manifests. If no signing key is provided, a new ECDSA key is generated when the registry starts. |
|
||||
| `enabled` | no | If this is not set to true, `schema1` manifests cannot be pushed. |
|
||||
|
||||
## `validation`
|
||||
|
||||
```none
|
||||
|
|
|
@ -152,16 +152,16 @@ group unrelated events and send them in the same envelope to reduce the total
|
|||
number of requests.
|
||||
|
||||
The full package has the mediatype
|
||||
"application/vnd.docker.distribution.events.v1+json", which is set on the
|
||||
"application/vnd.docker.distribution.events.v2+json", which is set on the
|
||||
request coming to an endpoint.
|
||||
|
||||
An example of a full event may look as follows:
|
||||
|
||||
```http request
|
||||
POST /callback HTTP/1.1
|
||||
Host: application/vnd.docker.distribution.events.v1+json
|
||||
Host: application/vnd.docker.distribution.events.v2+json
|
||||
Authorization: Bearer <your token, if needed>
|
||||
Content-Type: application/vnd.docker.distribution.events.v1+json
|
||||
Content-Type: application/vnd.docker.distribution.events.v2+json
|
||||
|
||||
{
|
||||
"events": [
|
||||
|
@ -170,7 +170,7 @@ Content-Type: application/vnd.docker.distribution.events.v1+json
|
|||
"timestamp": "2006-01-02T15:04:05Z",
|
||||
"action": "push",
|
||||
"target": {
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v1+json",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:fea8895f450959fa676bcc1df0611ea93823a735a01205fd8622846041d0c7cf",
|
||||
"length": 1,
|
||||
"repository": "library/test",
|
||||
|
|
|
@ -1,170 +0,0 @@
|
|||
---
|
||||
title: "Image Manifest V 2, Schema 1 "
|
||||
description: "image manifest for the Registry."
|
||||
keywords: registry, on-prem, images, tags, repository, distribution, api, advanced, manifest
|
||||
---
|
||||
|
||||
# Image Manifest Version 2, Schema 1
|
||||
|
||||
> **Deprecated**
|
||||
>
|
||||
> Image Manifest v2, Schema 1 is deprecated. Use [Image Manifest v2, Schema 2](manifest-v2-2.md),
|
||||
> or the [OCI Image Specification](https://github.com/opencontainers/image-spec).
|
||||
> Image Manifest v2, Schema 1 should not be used for purposes other than backward
|
||||
> compatibility.
|
||||
|
||||
This document outlines the format of the V2 image manifest. The image
|
||||
manifest described herein was introduced in the Docker daemon in the [v1.3.0
|
||||
release](https://github.com/docker/docker/commit/9f482a66ab37ec396ac61ed0c00d59122ac07453).
|
||||
It is a provisional manifest to provide a compatibility with the [V1 Image
|
||||
format](https://github.com/docker/docker/blob/master/image/spec/v1.md), as the
|
||||
requirements are defined for the [V2 Schema 2
|
||||
image](https://github.com/distribution/distribution/pull/62).
|
||||
|
||||
|
||||
Image manifests describe the various constituents of a docker image. Image
|
||||
manifests can be serialized to JSON format with the following media types:
|
||||
|
||||
Manifest Type | Media Type
|
||||
------------- | -------------
|
||||
manifest | "application/vnd.docker.distribution.manifest.v1+json"
|
||||
signed manifest | "application/vnd.docker.distribution.manifest.v1+prettyjws"
|
||||
|
||||
*Note that "application/json" will also be accepted for schema 1.*
|
||||
|
||||
References:
|
||||
|
||||
- [Proposal: JSON Registry API V2.1](https://github.com/docker/docker/issues/9015)
|
||||
- [Proposal: Provenance step 1 - Transform images for validation and verification](https://github.com/docker/docker/issues/8093)
|
||||
|
||||
## *Manifest* Field Descriptions
|
||||
|
||||
Manifest provides the base accessible fields for working with V2 image format
|
||||
in the registry.
|
||||
|
||||
- **`name`** *string*
|
||||
|
||||
name is the name of the image's repository
|
||||
|
||||
- **`tag`** *string*
|
||||
|
||||
tag is the tag of the image
|
||||
|
||||
- **`architecture`** *string*
|
||||
|
||||
architecture is the host architecture on which this image is intended to
|
||||
run. This is for information purposes and not currently used by the engine
|
||||
|
||||
- **`fsLayers`** *array*
|
||||
|
||||
fsLayers is a list of filesystem layer blob sums contained in this image.
|
||||
|
||||
An fsLayer is a struct consisting of the following fields
|
||||
- **`blobSum`** *digest.Digest*
|
||||
|
||||
blobSum is the digest of the referenced filesystem image layer. A
|
||||
digest must be a sha256 hash.
|
||||
|
||||
|
||||
- **`history`** *array*
|
||||
|
||||
history is a list of unstructured historical data for v1 compatibility. It
|
||||
contains ID of the image layer and ID of the layer's parent layers.
|
||||
|
||||
history is a struct consisting of the following fields
|
||||
- **`v1Compatibility`** string
|
||||
|
||||
V1Compatibility is the raw V1 compatibility information. This will
|
||||
contain the JSON object describing the V1 of this image.
|
||||
|
||||
- **`schemaVersion`** *int*
|
||||
|
||||
SchemaVersion is the image manifest schema that this image follows.
|
||||
|
||||
>**Note**:the length of `history` must be equal to the length of `fsLayers` and
|
||||
>entries in each are correlated by index.
|
||||
|
||||
## Signed Manifests
|
||||
|
||||
Signed manifests provides an envelope for a signed image manifest. A signed
|
||||
manifest consists of an image manifest along with an additional field
|
||||
containing the signature of the manifest.
|
||||
|
||||
The docker client can verify signed manifests and displays a message to the user.
|
||||
|
||||
### Signing Manifests
|
||||
|
||||
Image manifests can be signed in two different ways: with a *libtrust* private
|
||||
key or an x509 certificate chain. When signing with an x509 certificate chain,
|
||||
the public key of the first element in the chain must be the public key
|
||||
corresponding with the sign key.
|
||||
|
||||
### Signed Manifest Field Description
|
||||
|
||||
Signed manifests include an image manifest and a list of signatures generated
|
||||
by *libtrust*. A signature consists of the following fields:
|
||||
|
||||
|
||||
- **`header`** *[JOSE](http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2)*
|
||||
|
||||
A [JSON Web Signature](http://self-issued.info/docs/draft-ietf-jose-json-web-signature.html)
|
||||
|
||||
- **`signature`** *string*
|
||||
|
||||
A signature for the image manifest, signed by a *libtrust* private key
|
||||
|
||||
- **`protected`** *string*
|
||||
|
||||
The signed protected header
|
||||
|
||||
## Example Manifest
|
||||
|
||||
*Example showing the official 'hello-world' image manifest.*
|
||||
|
||||
```
|
||||
{
|
||||
"name": "hello-world",
|
||||
"tag": "latest",
|
||||
"architecture": "amd64",
|
||||
"fsLayers": [
|
||||
{
|
||||
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
|
||||
},
|
||||
{
|
||||
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
|
||||
},
|
||||
{
|
||||
"blobSum": "sha256:cc8567d70002e957612902a8e985ea129d831ebe04057d88fb644857caa45d11"
|
||||
},
|
||||
{
|
||||
"blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
|
||||
}
|
||||
],
|
||||
"history": [
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5\",\"parent\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"created\":\"2014-12-31T22:57:59.178729048Z\",\"container\":\"27b45f8fb11795b52e9605b686159729b0d9ca92f76d40fb4f05a62e19c46b4f\",\"container_config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/hello]\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
|
||||
},
|
||||
{
|
||||
"v1Compatibility": "{\"id\":\"e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5\",\"parent\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"created\":\"2014-12-31T22:57:59.178729048Z\",\"container\":\"27b45f8fb11795b52e9605b686159729b0d9ca92f76d40fb4f05a62e19c46b4f\",\"container_config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [/hello]\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"docker_version\":\"1.4.1\",\"config\":{\"Hostname\":\"8ce6509d66e2\",\"Domainname\":\"\",\"User\":\"\",\"Memory\":0,\"MemorySwap\":0,\"CpuShares\":0,\"Cpuset\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"PortSpecs\":null,\"ExposedPorts\":null,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[\"/hello\"],\"Image\":\"31cbccb51277105ba3ae35ce33c22b69c9e3f1002e76e4c736a2e8ebff9d7b5d\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":[],\"SecurityOpt\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 1,
|
||||
"signatures": [
|
||||
{
|
||||
"header": {
|
||||
"jwk": {
|
||||
"crv": "P-256",
|
||||
"kid": "OD6I:6DRK:JXEJ:KBM4:255X:NSAA:MUSF:E4VM:ZI6W:CUN2:L4Z6:LSF4",
|
||||
"kty": "EC",
|
||||
"x": "3gAwX48IQ5oaYQAYSxor6rYYc_6yjuLCjtQ9LUakg4A",
|
||||
"y": "t72ge6kIA1XOjqjVoEOiPPAURltJFBMGDSQvEGVB010"
|
||||
},
|
||||
"alg": "ES256"
|
||||
},
|
||||
"signature": "XREm0L8WNn27Ga_iE_vRnTxVMhhYY0Zst_FfkKopg6gWSoTOZTuW4rK0fg_IqnKkEKlbD83tD46LKEGi5aIVFg",
|
||||
"protected": "eyJmb3JtYXRMZW5ndGgiOjY2MjgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNS0wNC0wOFQxODo1Mjo1OVoifQ"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
|
@ -24,7 +24,6 @@ an ID for the image.
|
|||
The following media types are used by the manifest formats described here, and
|
||||
the resources they reference:
|
||||
|
||||
- `application/vnd.docker.distribution.manifest.v1+json`: schema1 (existing manifest format)
|
||||
- `application/vnd.docker.distribution.manifest.v2+json`: New image manifest format (schemaVersion = 2)
|
||||
- `application/vnd.docker.distribution.manifest.list.v2+json`: Manifest list, aka "fat manifest"
|
||||
- `application/vnd.docker.container.image.v1+json`: Container config JSON
|
||||
|
@ -60,9 +59,7 @@ image manifest based on the Content-Type returned in the HTTP response.
|
|||
- **`mediaType`** *string*
|
||||
|
||||
The MIME type of the referenced object. This will generally be
|
||||
`application/vnd.docker.distribution.manifest.v2+json`, but it could also
|
||||
be `application/vnd.docker.distribution.manifest.v1+json` if the manifest
|
||||
list references a legacy schema-1 manifest.
|
||||
`application/vnd.docker.distribution.manifest.v2+json`.
|
||||
|
||||
- **`size`** *int*
|
||||
|
||||
|
|
|
@ -1,303 +0,0 @@
|
|||
package schema1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/distribution/distribution/v3"
|
||||
"github.com/distribution/distribution/v3/manifest"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
"github.com/docker/libtrust"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
type diffID digest.Digest
|
||||
|
||||
// gzippedEmptyTar is a gzip-compressed version of an empty tar file
|
||||
// (1024 NULL bytes)
|
||||
var gzippedEmptyTar = []byte{
|
||||
31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88,
|
||||
0, 8, 0, 0, 255, 255, 46, 175, 181, 239, 0, 4, 0, 0,
|
||||
}
|
||||
|
||||
// digestSHA256GzippedEmptyTar is the canonical sha256 digest of
|
||||
// gzippedEmptyTar
|
||||
const digestSHA256GzippedEmptyTar = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
|
||||
|
||||
// configManifestBuilder is a type for constructing manifests from an image
|
||||
// configuration and generic descriptors.
|
||||
type configManifestBuilder struct {
|
||||
// bs is a BlobService used to create empty layer tars in the
|
||||
// blob store if necessary.
|
||||
bs distribution.BlobService
|
||||
// pk is the libtrust private key used to sign the final manifest.
|
||||
pk libtrust.PrivateKey
|
||||
// configJSON is configuration supplied when the ManifestBuilder was
|
||||
// created.
|
||||
configJSON []byte
|
||||
// ref contains the name and optional tag provided to NewConfigManifestBuilder.
|
||||
ref reference.Named
|
||||
// descriptors is the set of descriptors referencing the layers.
|
||||
descriptors []distribution.Descriptor
|
||||
// emptyTarDigest is set to a valid digest if an empty tar has been
|
||||
// put in the blob store; otherwise it is empty.
|
||||
emptyTarDigest digest.Digest
|
||||
}
|
||||
|
||||
// NewConfigManifestBuilder is used to build new manifests for the current
|
||||
// schema version from an image configuration and a set of descriptors.
|
||||
// It takes a BlobService so that it can add an empty tar to the blob store
|
||||
// if the resulting manifest needs empty layers.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015,
|
||||
// use Docker Image Manifest v2, Schema 2, or the OCI Image Specification v1.
|
||||
// This package should not be used for purposes other than backward compatibility.
|
||||
func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, ref reference.Named, configJSON []byte) distribution.ManifestBuilder {
|
||||
return &configManifestBuilder{
|
||||
bs: bs,
|
||||
pk: pk,
|
||||
configJSON: configJSON,
|
||||
ref: ref,
|
||||
}
|
||||
}
|
||||
|
||||
// Build produces a final manifest from the given references.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Manifest, err error) {
|
||||
type imageRootFS struct {
|
||||
Type string `json:"type"`
|
||||
DiffIDs []diffID `json:"diff_ids,omitempty"`
|
||||
BaseLayer string `json:"base_layer,omitempty"`
|
||||
}
|
||||
|
||||
type imageHistory struct {
|
||||
Created time.Time `json:"created"`
|
||||
Author string `json:"author,omitempty"`
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
EmptyLayer bool `json:"empty_layer,omitempty"`
|
||||
}
|
||||
|
||||
type imageConfig struct {
|
||||
RootFS *imageRootFS `json:"rootfs,omitempty"`
|
||||
History []imageHistory `json:"history,omitempty"`
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
}
|
||||
|
||||
var img imageConfig
|
||||
|
||||
if err := json.Unmarshal(mb.configJSON, &img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(img.History) == 0 {
|
||||
return nil, errors.New("empty history when trying to create schema1 manifest")
|
||||
}
|
||||
|
||||
if len(img.RootFS.DiffIDs) != len(mb.descriptors) {
|
||||
return nil, fmt.Errorf("number of descriptors and number of layers in rootfs must match: len(%v) != len(%v)", img.RootFS.DiffIDs, mb.descriptors)
|
||||
}
|
||||
|
||||
// Generate IDs for each layer
|
||||
// For non-top-level layers, create fake V1Compatibility strings that
|
||||
// fit the format and don't collide with anything else, but don't
|
||||
// result in runnable images on their own.
|
||||
type v1Compatibility struct {
|
||||
ID string `json:"id"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
ContainerConfig struct {
|
||||
Cmd []string
|
||||
} `json:"container_config,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
ThrowAway bool `json:"throwaway,omitempty"`
|
||||
}
|
||||
|
||||
fsLayerList := make([]FSLayer, len(img.History))
|
||||
history := make([]History, len(img.History))
|
||||
|
||||
parent := ""
|
||||
layerCounter := 0
|
||||
for i, h := range img.History[:len(img.History)-1] {
|
||||
var blobsum digest.Digest
|
||||
if h.EmptyLayer {
|
||||
if blobsum, err = mb.emptyTar(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if len(img.RootFS.DiffIDs) <= layerCounter {
|
||||
return nil, errors.New("too many non-empty layers in History section")
|
||||
}
|
||||
blobsum = mb.descriptors[layerCounter].Digest
|
||||
layerCounter++
|
||||
}
|
||||
|
||||
v1ID := digest.FromBytes([]byte(blobsum.Encoded() + " " + parent)).Encoded()
|
||||
|
||||
if i == 0 && img.RootFS.BaseLayer != "" {
|
||||
// windows-only baselayer setup
|
||||
baseID := sha512.Sum384([]byte(img.RootFS.BaseLayer))
|
||||
parent = fmt.Sprintf("%x", baseID[:32])
|
||||
}
|
||||
|
||||
v1Compat := v1Compatibility{
|
||||
ID: v1ID,
|
||||
Parent: parent,
|
||||
Comment: h.Comment,
|
||||
Created: h.Created,
|
||||
Author: h.Author,
|
||||
}
|
||||
v1Compat.ContainerConfig.Cmd = []string{img.History[i].CreatedBy}
|
||||
if h.EmptyLayer {
|
||||
v1Compat.ThrowAway = true
|
||||
}
|
||||
jsonBytes, err := json.Marshal(&v1Compat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reversedIndex := len(img.History) - i - 1
|
||||
history[reversedIndex].V1Compatibility = string(jsonBytes)
|
||||
fsLayerList[reversedIndex] = FSLayer{BlobSum: blobsum}
|
||||
|
||||
parent = v1ID
|
||||
}
|
||||
|
||||
latestHistory := img.History[len(img.History)-1]
|
||||
|
||||
var blobsum digest.Digest
|
||||
if latestHistory.EmptyLayer {
|
||||
if blobsum, err = mb.emptyTar(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if len(img.RootFS.DiffIDs) <= layerCounter {
|
||||
return nil, errors.New("too many non-empty layers in History section")
|
||||
}
|
||||
blobsum = mb.descriptors[layerCounter].Digest
|
||||
}
|
||||
|
||||
fsLayerList[0] = FSLayer{BlobSum: blobsum}
|
||||
dgst := digest.FromBytes([]byte(blobsum.Encoded() + " " + parent + " " + string(mb.configJSON)))
|
||||
|
||||
// Top-level v1compatibility string should be a modified version of the
|
||||
// image config.
|
||||
transformedConfig, err := MakeV1ConfigFromConfig(mb.configJSON, dgst.Encoded(), parent, latestHistory.EmptyLayer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
history[0].V1Compatibility = string(transformedConfig)
|
||||
|
||||
tag := ""
|
||||
if tagged, isTagged := mb.ref.(reference.Tagged); isTagged {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
|
||||
mfst := Manifest{
|
||||
Versioned: manifest.Versioned{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
Name: mb.ref.Name(),
|
||||
Tag: tag,
|
||||
Architecture: img.Architecture,
|
||||
FSLayers: fsLayerList,
|
||||
History: history,
|
||||
}
|
||||
|
||||
return Sign(&mfst, mb.pk)
|
||||
}
|
||||
|
||||
// emptyTar pushes a compressed empty tar to the blob store if one doesn't
|
||||
// already exist, and returns its blobsum.
|
||||
func (mb *configManifestBuilder) emptyTar(ctx context.Context) (digest.Digest, error) {
|
||||
if mb.emptyTarDigest != "" {
|
||||
// Already put an empty tar
|
||||
return mb.emptyTarDigest, nil
|
||||
}
|
||||
|
||||
descriptor, err := mb.bs.Stat(ctx, digestSHA256GzippedEmptyTar)
|
||||
switch err {
|
||||
case nil:
|
||||
mb.emptyTarDigest = descriptor.Digest
|
||||
return descriptor.Digest, nil
|
||||
case distribution.ErrBlobUnknown:
|
||||
// nop
|
||||
default:
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Add gzipped empty tar to the blob store
|
||||
descriptor, err = mb.bs.Put(ctx, "", gzippedEmptyTar)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
mb.emptyTarDigest = descriptor.Digest
|
||||
|
||||
return descriptor.Digest, nil
|
||||
}
|
||||
|
||||
// AppendReference adds a reference to the current ManifestBuilder.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (mb *configManifestBuilder) AppendReference(d distribution.Describable) error {
|
||||
descriptor := d.Descriptor()
|
||||
|
||||
if err := descriptor.Digest.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mb.descriptors = append(mb.descriptors, descriptor)
|
||||
return nil
|
||||
}
|
||||
|
||||
// References returns the current references added to this builder.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (mb *configManifestBuilder) References() []distribution.Descriptor {
|
||||
return mb.descriptors
|
||||
}
|
||||
|
||||
// MakeV1ConfigFromConfig creates an legacy V1 image config from image config JSON.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func MakeV1ConfigFromConfig(configJSON []byte, v1ID, parentV1ID string, throwaway bool) ([]byte, error) {
|
||||
// Top-level v1compatibility string should be a modified version of the
|
||||
// image config.
|
||||
var configAsMap map[string]*json.RawMessage
|
||||
if err := json.Unmarshal(configJSON, &configAsMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Delete fields that didn't exist in old manifest
|
||||
delete(configAsMap, "rootfs")
|
||||
delete(configAsMap, "history")
|
||||
configAsMap["id"] = rawJSON(v1ID)
|
||||
if parentV1ID != "" {
|
||||
configAsMap["parent"] = rawJSON(parentV1ID)
|
||||
}
|
||||
if throwaway {
|
||||
configAsMap["throwaway"] = rawJSON(true)
|
||||
}
|
||||
|
||||
return json.Marshal(configAsMap)
|
||||
}
|
||||
|
||||
func rawJSON(value interface{}) *json.RawMessage {
|
||||
jsonval, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return (*json.RawMessage)(&jsonval)
|
||||
}
|
|
@ -1,262 +0,0 @@
|
|||
package schema1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/distribution/distribution/v3"
|
||||
dcontext "github.com/distribution/distribution/v3/context"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
"github.com/docker/libtrust"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
type mockBlobService struct {
|
||||
descriptors map[digest.Digest]distribution.Descriptor
|
||||
distribution.BlobService
|
||||
}
|
||||
|
||||
func (bs *mockBlobService) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||
if descriptor, ok := bs.descriptors[dgst]; ok {
|
||||
return descriptor, nil
|
||||
}
|
||||
return distribution.Descriptor{}, distribution.ErrBlobUnknown
|
||||
}
|
||||
|
||||
func (bs *mockBlobService) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) {
|
||||
d := distribution.Descriptor{
|
||||
MediaType: mediaType,
|
||||
Digest: digest.FromBytes(p),
|
||||
Size: int64(len(p)),
|
||||
}
|
||||
bs.descriptors[d.Digest] = d
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func TestEmptyTar(t *testing.T) {
|
||||
// Confirm that gzippedEmptyTar expands to 1024 NULL bytes.
|
||||
var decompressed [2048]byte
|
||||
gzipReader, err := gzip.NewReader(bytes.NewReader(gzippedEmptyTar))
|
||||
if err != nil {
|
||||
t.Fatalf("NewReader returned error: %v", err)
|
||||
}
|
||||
n, _ := gzipReader.Read(decompressed[:])
|
||||
if n != 1024 {
|
||||
t.Fatalf("read returned %d bytes; expected 1024", n)
|
||||
}
|
||||
n, err = gzipReader.Read(decompressed[1024:])
|
||||
if n != 0 {
|
||||
t.Fatalf("read returned %d bytes; expected 0", n)
|
||||
}
|
||||
if err != io.EOF {
|
||||
t.Fatal("read did not return io.EOF")
|
||||
}
|
||||
gzipReader.Close()
|
||||
for _, b := range decompressed[:1024] {
|
||||
if b != 0 {
|
||||
t.Fatal("nonzero byte in decompressed tar")
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm that digestSHA256EmptyTar is the digest of gzippedEmptyTar.
|
||||
dgst := digest.FromBytes(gzippedEmptyTar)
|
||||
if dgst != digestSHA256GzippedEmptyTar {
|
||||
t.Fatalf("digest mismatch for empty tar: expected %s got %s", digestSHA256GzippedEmptyTar, dgst)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigBuilder(t *testing.T) {
|
||||
imgJSON := `{
|
||||
"architecture": "amd64",
|
||||
"config": {
|
||||
"AttachStderr": false,
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": false,
|
||||
"Cmd": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"echo hi"
|
||||
],
|
||||
"Domainname": "",
|
||||
"Entrypoint": null,
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"derived=true",
|
||||
"asdf=true"
|
||||
],
|
||||
"Hostname": "23304fc829f9",
|
||||
"Image": "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246",
|
||||
"Labels": {},
|
||||
"OnBuild": [],
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Tty": false,
|
||||
"User": "",
|
||||
"Volumes": null,
|
||||
"WorkingDir": ""
|
||||
},
|
||||
"container": "e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001",
|
||||
"container_config": {
|
||||
"AttachStderr": false,
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": false,
|
||||
"Cmd": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]"
|
||||
],
|
||||
"Domainname": "",
|
||||
"Entrypoint": null,
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"derived=true",
|
||||
"asdf=true"
|
||||
],
|
||||
"Hostname": "23304fc829f9",
|
||||
"Image": "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246",
|
||||
"Labels": {},
|
||||
"OnBuild": [],
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Tty": false,
|
||||
"User": "",
|
||||
"Volumes": null,
|
||||
"WorkingDir": ""
|
||||
},
|
||||
"created": "2015-11-04T23:06:32.365666163Z",
|
||||
"docker_version": "1.9.0-dev",
|
||||
"history": [
|
||||
{
|
||||
"created": "2015-10-31T22:22:54.690851953Z",
|
||||
"created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
|
||||
},
|
||||
{
|
||||
"created": "2015-10-31T22:22:55.613815829Z",
|
||||
"created_by": "/bin/sh -c #(nop) CMD [\"sh\"]"
|
||||
},
|
||||
{
|
||||
"created": "2015-11-04T23:06:30.934316144Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV derived=true",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2015-11-04T23:06:31.192097572Z",
|
||||
"created_by": "/bin/sh -c #(nop) ENV asdf=true",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"author": "Alyssa P. Hacker \u003calyspdev@example.com\u003e",
|
||||
"created": "2015-11-04T23:06:32.083868454Z",
|
||||
"created_by": "/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024"
|
||||
},
|
||||
{
|
||||
"created": "2015-11-04T23:06:32.365666163Z",
|
||||
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]",
|
||||
"empty_layer": true
|
||||
}
|
||||
],
|
||||
"os": "linux",
|
||||
"rootfs": {
|
||||
"diff_ids": [
|
||||
"sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
|
||||
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
|
||||
"sha256:13f53e08df5a220ab6d13c58b2bf83a59cbdc2e04d0a3f041ddf4b0ba4112d49"
|
||||
],
|
||||
"type": "layers"
|
||||
}
|
||||
}`
|
||||
|
||||
descriptors := []distribution.Descriptor{
|
||||
{Digest: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
|
||||
{Digest: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")},
|
||||
{Digest: digest.Digest("sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
|
||||
}
|
||||
|
||||
pk, err := libtrust.GenerateECP256PrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("could not generate key for testing: %v", err)
|
||||
}
|
||||
|
||||
bs := &mockBlobService{descriptors: make(map[digest.Digest]distribution.Descriptor)}
|
||||
|
||||
ref, err := reference.WithName("testrepo")
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse reference: %v", err)
|
||||
}
|
||||
ref, err = reference.WithTag(ref, "testtag")
|
||||
if err != nil {
|
||||
t.Fatalf("could not add tag: %v", err)
|
||||
}
|
||||
|
||||
builder := NewConfigManifestBuilder(bs, pk, ref, []byte(imgJSON))
|
||||
|
||||
for _, d := range descriptors {
|
||||
if err := builder.AppendReference(d); err != nil {
|
||||
t.Fatalf("AppendReference returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
signed, err := builder.Build(dcontext.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Build returned error: %v", err)
|
||||
}
|
||||
|
||||
// Check that the gzipped empty layer tar was put in the blob store
|
||||
_, err = bs.Stat(dcontext.Background(), digestSHA256GzippedEmptyTar)
|
||||
if err != nil {
|
||||
t.Fatal("gzipped empty tar was not put in the blob store")
|
||||
}
|
||||
|
||||
manifest := signed.(*SignedManifest).Manifest
|
||||
|
||||
if manifest.Versioned.SchemaVersion != 1 {
|
||||
t.Fatal("SchemaVersion != 1")
|
||||
}
|
||||
if manifest.Name != "testrepo" {
|
||||
t.Fatal("incorrect name in manifest")
|
||||
}
|
||||
if manifest.Tag != "testtag" {
|
||||
t.Fatal("incorrect tag in manifest")
|
||||
}
|
||||
if manifest.Architecture != "amd64" {
|
||||
t.Fatal("incorrect arch in manifest")
|
||||
}
|
||||
|
||||
expectedFSLayers := []FSLayer{
|
||||
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
|
||||
{BlobSum: digest.Digest("sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
|
||||
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
|
||||
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
|
||||
{BlobSum: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")},
|
||||
{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
|
||||
}
|
||||
|
||||
if len(manifest.FSLayers) != len(expectedFSLayers) {
|
||||
t.Fatalf("wrong number of FSLayers: %d", len(manifest.FSLayers))
|
||||
}
|
||||
if !reflect.DeepEqual(manifest.FSLayers, expectedFSLayers) {
|
||||
t.Fatal("wrong FSLayers list")
|
||||
}
|
||||
|
||||
expectedV1Compatibility := []string{
|
||||
`{"architecture":"amd64","config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","echo hi"],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","derived=true","asdf=true"],"Hostname":"23304fc829f9","Image":"sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246","Labels":{},"OnBuild":[],"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"container":"e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001","container_config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]"],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","derived=true","asdf=true"],"Hostname":"23304fc829f9","Image":"sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246","Labels":{},"OnBuild":[],"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"created":"2015-11-04T23:06:32.365666163Z","docker_version":"1.9.0-dev","id":"69e5c1bfadad697fdb6db59f6326648fa119e0c031a0eda33b8cfadcab54ba7f","os":"linux","parent":"74cf9c92699240efdba1903c2748ef57105d5bedc588084c4e88f3bb1c3ef0b0","throwaway":true}`,
|
||||
`{"id":"74cf9c92699240efdba1903c2748ef57105d5bedc588084c4e88f3bb1c3ef0b0","parent":"178be37afc7c49e951abd75525dbe0871b62ad49402f037164ee6314f754599d","created":"2015-11-04T23:06:32.083868454Z","container_config":{"Cmd":["/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024"]},"author":"Alyssa P. Hacker \u003calyspdev@example.com\u003e"}`,
|
||||
`{"id":"178be37afc7c49e951abd75525dbe0871b62ad49402f037164ee6314f754599d","parent":"b449305a55a283538c4574856a8b701f2a3d5ec08ef8aec47f385f20339a4866","created":"2015-11-04T23:06:31.192097572Z","container_config":{"Cmd":["/bin/sh -c #(nop) ENV asdf=true"]},"throwaway":true}`,
|
||||
`{"id":"b449305a55a283538c4574856a8b701f2a3d5ec08ef8aec47f385f20339a4866","parent":"9e3447ca24cb96d86ebd5960cb34d1299b07e0a0e03801d90b9969a2c187dd6e","created":"2015-11-04T23:06:30.934316144Z","container_config":{"Cmd":["/bin/sh -c #(nop) ENV derived=true"]},"throwaway":true}`,
|
||||
`{"id":"9e3447ca24cb96d86ebd5960cb34d1299b07e0a0e03801d90b9969a2c187dd6e","parent":"3690474eb5b4b26fdfbd89c6e159e8cc376ca76ef48032a30fa6aafd56337880","created":"2015-10-31T22:22:55.613815829Z","container_config":{"Cmd":["/bin/sh -c #(nop) CMD [\"sh\"]"]}}`,
|
||||
`{"id":"3690474eb5b4b26fdfbd89c6e159e8cc376ca76ef48032a30fa6aafd56337880","created":"2015-10-31T22:22:54.690851953Z","container_config":{"Cmd":["/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"]}}`,
|
||||
}
|
||||
|
||||
if len(manifest.History) != len(expectedV1Compatibility) {
|
||||
t.Fatalf("wrong number of history entries: %d", len(manifest.History))
|
||||
}
|
||||
for i := range expectedV1Compatibility {
|
||||
if manifest.History[i].V1Compatibility != expectedV1Compatibility[i] {
|
||||
t.Errorf("wrong V1Compatibility %d. expected:\n%s\ngot:\n%s", i, expectedV1Compatibility[i], manifest.History[i].V1Compatibility)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,226 +0,0 @@
|
|||
// Package schema1 provides definitions for the deprecated Docker Image
|
||||
// Manifest v2, Schema 1 specification.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. This package should not be used for purposes
|
||||
// other than backward compatibility.
|
||||
package schema1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/distribution/distribution/v3"
|
||||
"github.com/distribution/distribution/v3/manifest"
|
||||
"github.com/docker/libtrust"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// MediaTypeManifest specifies the mediaType for the current version. Note
|
||||
// that for schema version 1, the the media is optionally "application/json".
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
const MediaTypeManifest = "application/vnd.docker.distribution.manifest.v1+json"
|
||||
|
||||
// MediaTypeSignedManifest specifies the mediatype for current SignedManifest version
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
const MediaTypeSignedManifest = "application/vnd.docker.distribution.manifest.v1+prettyjws"
|
||||
|
||||
// MediaTypeManifestLayer specifies the media type for manifest layers
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
const MediaTypeManifestLayer = "application/vnd.docker.container.image.rootfs.diff+x-gtar"
|
||||
|
||||
// SchemaVersion provides a pre-initialized version structure for this
|
||||
// packages version of the manifest.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
var SchemaVersion = manifest.Versioned{
|
||||
SchemaVersion: 1,
|
||||
}
|
||||
|
||||
func init() {
|
||||
schema1Func := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
|
||||
sm := new(SignedManifest)
|
||||
err := sm.UnmarshalJSON(b)
|
||||
if err != nil {
|
||||
return nil, distribution.Descriptor{}, err
|
||||
}
|
||||
|
||||
desc := distribution.Descriptor{
|
||||
MediaType: MediaTypeSignedManifest,
|
||||
Digest: digest.FromBytes(sm.Canonical),
|
||||
Size: int64(len(sm.Canonical)),
|
||||
}
|
||||
return sm, desc, err
|
||||
}
|
||||
err := distribution.RegisterManifestSchema(MediaTypeSignedManifest, schema1Func)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to register manifest: %s", err))
|
||||
}
|
||||
err = distribution.RegisterManifestSchema("", schema1Func)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to register manifest: %s", err))
|
||||
}
|
||||
err = distribution.RegisterManifestSchema("application/json", schema1Func)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unable to register manifest: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
// FSLayer is a container struct for BlobSums defined in an image manifest.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
type FSLayer struct {
|
||||
// BlobSum is the tarsum of the referenced filesystem image layer
|
||||
BlobSum digest.Digest `json:"blobSum"`
|
||||
}
|
||||
|
||||
// History stores unstructured v1 compatibility information.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
type History struct {
|
||||
// V1Compatibility is the raw v1 compatibility information
|
||||
V1Compatibility string `json:"v1Compatibility"`
|
||||
}
|
||||
|
||||
// Manifest provides the base accessible fields for working with V2 image
|
||||
// format in the registry.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
type Manifest struct {
|
||||
manifest.Versioned
|
||||
|
||||
// Name is the name of the image's repository
|
||||
Name string `json:"name"`
|
||||
|
||||
// Tag is the tag of the image specified by this manifest
|
||||
Tag string `json:"tag"`
|
||||
|
||||
// Architecture is the host architecture on which this image is intended to
|
||||
// run
|
||||
Architecture string `json:"architecture"`
|
||||
|
||||
// FSLayers is a list of filesystem layer blobSums contained in this image
|
||||
FSLayers []FSLayer `json:"fsLayers"`
|
||||
|
||||
// History is a list of unstructured historical data for v1 compatibility
|
||||
History []History `json:"history"`
|
||||
}
|
||||
|
||||
// SignedManifest provides an envelope for a signed image manifest, including
|
||||
// the format sensitive raw bytes.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
type SignedManifest struct {
|
||||
Manifest
|
||||
|
||||
// Canonical is the canonical byte representation of the ImageManifest,
|
||||
// without any attached signatures. The manifest byte
|
||||
// representation cannot change or it will have to be re-signed.
|
||||
Canonical []byte `json:"-"`
|
||||
|
||||
// all contains the byte representation of the Manifest including signatures
|
||||
// and is returned by Payload()
|
||||
all []byte
|
||||
}
|
||||
|
||||
// UnmarshalJSON populates a new SignedManifest struct from JSON data.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (sm *SignedManifest) UnmarshalJSON(b []byte) error {
|
||||
sm.all = make([]byte, len(b))
|
||||
// store manifest and signatures in all
|
||||
copy(sm.all, b)
|
||||
|
||||
jsig, err := libtrust.ParsePrettySignature(b, "signatures")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Resolve the payload in the manifest.
|
||||
bytes, err := jsig.Payload()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sm.Canonical stores the canonical manifest JSON
|
||||
sm.Canonical = make([]byte, len(bytes))
|
||||
copy(sm.Canonical, bytes)
|
||||
|
||||
// Unmarshal canonical JSON into Manifest object
|
||||
var mfst Manifest
|
||||
if err := json.Unmarshal(sm.Canonical, &mfst); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sm.Manifest = mfst
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// References returns the descriptors of this manifests references.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (sm SignedManifest) References() []distribution.Descriptor {
|
||||
dependencies := make([]distribution.Descriptor, len(sm.FSLayers))
|
||||
for i, fsLayer := range sm.FSLayers {
|
||||
dependencies[i] = distribution.Descriptor{
|
||||
MediaType: "application/vnd.docker.container.image.rootfs.diff+x-gtar",
|
||||
Digest: fsLayer.BlobSum,
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies
|
||||
}
|
||||
|
||||
// MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner
|
||||
// contents. Applications requiring a marshaled signed manifest should simply
|
||||
// use Raw directly, since the the content produced by json.Marshal will be
|
||||
// compacted and will fail signature checks.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (sm *SignedManifest) MarshalJSON() ([]byte, error) {
|
||||
if len(sm.all) > 0 {
|
||||
return sm.all, nil
|
||||
}
|
||||
|
||||
// If the raw data is not available, just dump the inner content.
|
||||
return json.Marshal(&sm.Manifest)
|
||||
}
|
||||
|
||||
// Payload returns the signed content of the signed manifest.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (sm SignedManifest) Payload() (string, []byte, error) {
|
||||
return MediaTypeSignedManifest, sm.all, nil
|
||||
}
|
||||
|
||||
// Signatures returns the signatures as provided by
|
||||
// (*libtrust.JSONSignature).Signatures. The byte slices are opaque jws
|
||||
// signatures.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (sm *SignedManifest) Signatures() ([][]byte, error) {
|
||||
jsig, err := libtrust.ParsePrettySignature(sm.all, "signatures")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Resolve the payload in the manifest.
|
||||
return jsig.Signatures()
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
package schema1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
||||
type testEnv struct {
|
||||
name, tag string
|
||||
invalidSigned *SignedManifest
|
||||
signed *SignedManifest
|
||||
pk libtrust.PrivateKey
|
||||
}
|
||||
|
||||
func TestManifestMarshaling(t *testing.T) {
|
||||
env := genEnv(t)
|
||||
|
||||
// Check that the all field is the same as json.MarshalIndent with these
|
||||
// parameters.
|
||||
expected, err := json.MarshalIndent(env.signed, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("error marshaling manifest: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(expected, env.signed.all) {
|
||||
t.Fatalf("manifest bytes not equal:\nexpected:\n%s\nactual:\n%s\n", string(expected), string(env.signed.all))
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestUnmarshaling(t *testing.T) {
|
||||
env := genEnv(t)
|
||||
|
||||
var signed SignedManifest
|
||||
if err := json.Unmarshal(env.signed.all, &signed); err != nil {
|
||||
t.Fatalf("error unmarshaling signed manifest: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(&signed, env.signed) {
|
||||
t.Fatalf("manifests are different after unmarshaling: %v != %v", signed, env.signed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestVerification(t *testing.T) {
|
||||
env := genEnv(t)
|
||||
|
||||
publicKeys, err := Verify(env.signed)
|
||||
if err != nil {
|
||||
t.Fatalf("error verifying manifest: %v", err)
|
||||
}
|
||||
|
||||
if len(publicKeys) == 0 {
|
||||
t.Fatalf("no public keys found in signature")
|
||||
}
|
||||
|
||||
var found bool
|
||||
publicKey := env.pk.PublicKey()
|
||||
// ensure that one of the extracted public keys matches the private key.
|
||||
for _, candidate := range publicKeys {
|
||||
if candidate.KeyID() == publicKey.KeyID() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatalf("expected public key, %v, not found in verified keys: %v", publicKey, publicKeys)
|
||||
}
|
||||
|
||||
// Check that an invalid manifest fails verification
|
||||
_, err = Verify(env.invalidSigned)
|
||||
if err != nil {
|
||||
t.Fatalf("Invalid manifest should not pass Verify()")
|
||||
}
|
||||
}
|
||||
|
||||
func genEnv(t *testing.T) *testEnv {
|
||||
pk, err := libtrust.GenerateECP256PrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("error generating test key: %v", err)
|
||||
}
|
||||
|
||||
name, tag := "foo/bar", "test"
|
||||
|
||||
invalid := Manifest{
|
||||
Versioned: SchemaVersion,
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
FSLayers: []FSLayer{
|
||||
{
|
||||
BlobSum: "asdf",
|
||||
},
|
||||
{
|
||||
BlobSum: "qwer",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
valid := Manifest{
|
||||
Versioned: SchemaVersion,
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
FSLayers: []FSLayer{
|
||||
{
|
||||
BlobSum: "asdf",
|
||||
},
|
||||
},
|
||||
History: []History{
|
||||
{
|
||||
V1Compatibility: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sm, err := Sign(&valid, pk)
|
||||
if err != nil {
|
||||
t.Fatalf("error signing manifest: %v", err)
|
||||
}
|
||||
|
||||
invalidSigned, err := Sign(&invalid, pk)
|
||||
if err != nil {
|
||||
t.Fatalf("error signing manifest: %v", err)
|
||||
}
|
||||
|
||||
return &testEnv{
|
||||
name: name,
|
||||
tag: tag,
|
||||
invalidSigned: invalidSigned,
|
||||
signed: sm,
|
||||
pk: pk,
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
package schema1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/distribution/distribution/v3"
|
||||
"github.com/distribution/distribution/v3/manifest"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
"github.com/docker/libtrust"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// referenceManifestBuilder is a type for constructing manifests from schema1
|
||||
// dependencies.
|
||||
type referenceManifestBuilder struct {
|
||||
Manifest
|
||||
pk libtrust.PrivateKey
|
||||
}
|
||||
|
||||
// NewReferenceManifestBuilder is used to build new manifests for the current
|
||||
// schema version using schema1 dependencies.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func NewReferenceManifestBuilder(pk libtrust.PrivateKey, ref reference.Named, architecture string) distribution.ManifestBuilder {
|
||||
tag := ""
|
||||
if tagged, isTagged := ref.(reference.Tagged); isTagged {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
|
||||
return &referenceManifestBuilder{
|
||||
Manifest: Manifest{
|
||||
Versioned: manifest.Versioned{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
Name: ref.Name(),
|
||||
Tag: tag,
|
||||
Architecture: architecture,
|
||||
},
|
||||
pk: pk,
|
||||
}
|
||||
}
|
||||
|
||||
func (mb *referenceManifestBuilder) Build(ctx context.Context) (distribution.Manifest, error) {
|
||||
m := mb.Manifest
|
||||
if len(m.FSLayers) == 0 {
|
||||
return nil, errors.New("cannot build manifest with zero layers or history")
|
||||
}
|
||||
|
||||
m.FSLayers = make([]FSLayer, len(mb.Manifest.FSLayers))
|
||||
m.History = make([]History, len(mb.Manifest.History))
|
||||
copy(m.FSLayers, mb.Manifest.FSLayers)
|
||||
copy(m.History, mb.Manifest.History)
|
||||
|
||||
return Sign(&m, mb.pk)
|
||||
}
|
||||
|
||||
// AppendReference adds a reference to the current ManifestBuilder.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (mb *referenceManifestBuilder) AppendReference(d distribution.Describable) error {
|
||||
r, ok := d.(Reference)
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to add non-reference type to v1 builder")
|
||||
}
|
||||
|
||||
// Entries need to be prepended
|
||||
mb.Manifest.FSLayers = append([]FSLayer{{BlobSum: r.Digest}}, mb.Manifest.FSLayers...)
|
||||
mb.Manifest.History = append([]History{r.History}, mb.Manifest.History...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// References returns the current references added to this builder.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (mb *referenceManifestBuilder) References() []distribution.Descriptor {
|
||||
refs := make([]distribution.Descriptor, len(mb.Manifest.FSLayers))
|
||||
for i := range mb.Manifest.FSLayers {
|
||||
layerDigest := mb.Manifest.FSLayers[i].BlobSum
|
||||
history := mb.Manifest.History[i]
|
||||
ref := Reference{layerDigest, 0, history}
|
||||
refs[i] = ref.Descriptor()
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
// Reference describes a Manifest v2, schema version 1 dependency.
|
||||
// An FSLayer associated with a history entry.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
type Reference struct {
|
||||
Digest digest.Digest
|
||||
Size int64 // if we know it, set it for the descriptor.
|
||||
History History
|
||||
}
|
||||
|
||||
// Descriptor describes a reference.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func (r Reference) Descriptor() distribution.Descriptor {
|
||||
return distribution.Descriptor{
|
||||
MediaType: MediaTypeManifestLayer,
|
||||
Digest: r.Digest,
|
||||
Size: r.Size,
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
package schema1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/distribution/distribution/v3/context"
|
||||
"github.com/distribution/distribution/v3/manifest"
|
||||
"github.com/distribution/distribution/v3/reference"
|
||||
"github.com/docker/libtrust"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func makeSignedManifest(t *testing.T, pk libtrust.PrivateKey, refs []Reference) *SignedManifest {
|
||||
u := &Manifest{
|
||||
Versioned: manifest.Versioned{
|
||||
SchemaVersion: 1,
|
||||
},
|
||||
Name: "foo/bar",
|
||||
Tag: "latest",
|
||||
Architecture: "amd64",
|
||||
}
|
||||
|
||||
for i := len(refs) - 1; i >= 0; i-- {
|
||||
u.FSLayers = append(u.FSLayers, FSLayer{
|
||||
BlobSum: refs[i].Digest,
|
||||
})
|
||||
u.History = append(u.History, History{
|
||||
V1Compatibility: refs[i].History.V1Compatibility,
|
||||
})
|
||||
}
|
||||
|
||||
signedManifest, err := Sign(u, pk)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error signing manifest: %v", err)
|
||||
}
|
||||
return signedManifest
|
||||
}
|
||||
|
||||
func TestReferenceBuilder(t *testing.T) {
|
||||
pk, err := libtrust.GenerateECP256PrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error generating private key: %v", err)
|
||||
}
|
||||
|
||||
r1 := Reference{
|
||||
Digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
Size: 1,
|
||||
History: History{V1Compatibility: "{\"a\" : 1 }"},
|
||||
}
|
||||
r2 := Reference{
|
||||
Digest: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
Size: 2,
|
||||
History: History{V1Compatibility: "{\"\a\" : 2 }"},
|
||||
}
|
||||
|
||||
handCrafted := makeSignedManifest(t, pk, []Reference{r1, r2})
|
||||
|
||||
ref, err := reference.WithName(handCrafted.Manifest.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse reference: %v", err)
|
||||
}
|
||||
ref, err = reference.WithTag(ref, handCrafted.Manifest.Tag)
|
||||
if err != nil {
|
||||
t.Fatalf("could not add tag: %v", err)
|
||||
}
|
||||
|
||||
b := NewReferenceManifestBuilder(pk, ref, handCrafted.Manifest.Architecture)
|
||||
_, err = b.Build(context.Background())
|
||||
if err == nil {
|
||||
t.Fatal("Expected error building zero length manifest")
|
||||
}
|
||||
|
||||
err = b.AppendReference(r1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = b.AppendReference(r2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
refs := b.References()
|
||||
if len(refs) != 2 {
|
||||
t.Fatalf("Unexpected reference count : %d != %d", 2, len(refs))
|
||||
}
|
||||
|
||||
// Ensure ordering
|
||||
if refs[0].Digest != r2.Digest {
|
||||
t.Fatalf("Unexpected reference : %v", refs[0])
|
||||
}
|
||||
|
||||
m, err := b.Build(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
built, ok := m.(*SignedManifest)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected type from Build() : %T", built)
|
||||
}
|
||||
|
||||
d1 := digest.FromBytes(built.Canonical)
|
||||
d2 := digest.FromBytes(handCrafted.Canonical)
|
||||
if d1 != d2 {
|
||||
t.Errorf("mismatching canonical JSON")
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
package schema1
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
||||
// Sign signs the manifest with the provided private key, returning a
|
||||
// SignedManifest. This typically won't be used within the registry, except
|
||||
// for testing.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func Sign(m *Manifest, pk libtrust.PrivateKey) (*SignedManifest, error) {
|
||||
p, err := json.MarshalIndent(m, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
js, err := libtrust.NewJSONSignature(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := js.Sign(pk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pretty, err := js.PrettySignature("signatures")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SignedManifest{
|
||||
Manifest: *m,
|
||||
all: pretty,
|
||||
Canonical: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignWithChain signs the manifest with the given private key and x509 chain.
|
||||
// The public key of the first element in the chain must be the public key
|
||||
// corresponding with the sign key.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func SignWithChain(m *Manifest, key libtrust.PrivateKey, chain []*x509.Certificate) (*SignedManifest, error) {
|
||||
p, err := json.MarshalIndent(m, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
js, err := libtrust.NewJSONSignature(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := js.SignWithChain(key, chain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pretty, err := js.PrettySignature("signatures")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SignedManifest{
|
||||
Manifest: *m,
|
||||
all: pretty,
|
||||
Canonical: p,
|
||||
}, nil
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package schema1
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/docker/libtrust"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Verify verifies the signature of the signed manifest returning the public
|
||||
// keys used during signing.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func Verify(sm *SignedManifest) ([]libtrust.PublicKey, error) {
|
||||
js, err := libtrust.ParsePrettySignature(sm.all, "signatures")
|
||||
if err != nil {
|
||||
logrus.WithField("err", err).Debugf("(*SignedManifest).Verify")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return js.Verify()
|
||||
}
|
||||
|
||||
// VerifyChains verifies the signature of the signed manifest against the
|
||||
// certificate pool returning the list of verified chains. Signatures without
|
||||
// an x509 chain are not checked.
|
||||
//
|
||||
// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015.
|
||||
// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification.
|
||||
func VerifyChains(sm *SignedManifest, ca *x509.CertPool) ([][]*x509.Certificate, error) {
|
||||
js, err := libtrust.ParsePrettySignature(sm.all, "signatures")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return js.VerifyChains(ca)
|
||||
}
|
|
@ -20,7 +20,7 @@ const (
|
|||
// EventsMediaType is the mediatype for the json event envelope. If the
|
||||
// Event, ActorRecord, SourceRecord or Envelope structs change, the version
|
||||
// number should be incremented.
|
||||
EventsMediaType = "application/vnd.docker.distribution.events.v1+json"
|
||||
EventsMediaType = "application/vnd.docker.distribution.events.v2+json"
|
||||
// LayerMediaType is the media type for image rootfs diffs (aka "layers")
|
||||
// used by Docker. We don't expect this to change for quite a while.
|
||||
layerMediaType = "application/vnd.docker.container.image.rootfs.diff+x-gtar"
|
||||
|
|
Loading…
Reference in a new issue