// digestPathComponents provides a consistent path breakdown for a given // digest. For a generic digest, it will be as follows: // // <algorithm>/<hex digest> // // Most importantly, for tarsum, the layout looks like this: // // tarsum/<version>/<digest algorithm>/<full digest> // // If multilevel is true, the first two bytes of the digest will separate // groups of digest folder. It will be as follows: // // <algorithm>/<first two bytes of digest>/<full digest> // func digestPathComponents(dgst digest.Digest, multilevel bool) ([]string, error) { if err := dgst.Validate(); err != nil { return nil, err } algorithm := blobAlgorithmReplacer.Replace(string(dgst.Algorithm())) hex := dgst.Hex() prefix := []string{algorithm} var suffix []string if multilevel { suffix = append(suffix, hex[:2]) } suffix = append(suffix, hex) if tsi, err := digest.ParseTarSum(dgst.String()); err == nil { // We have a tarsum! version := tsi.Version if version == "" { version = "v0" } prefix = []string{ "tarsum", version, tsi.Algorithm, } } return append(prefix, suffix...), nil }
func (graph *Graph) setLayerDigest(id string, dgst digest.Digest) error { root := graph.imageRoot(id) if err := ioutil.WriteFile(filepath.Join(root, digestFileName), []byte(dgst.String()), 0600); err != nil { return fmt.Errorf("Error storing digest in %s/%s: %s", root, digestFileName, err) } return nil }
func (pms proxyManifestStore) Get(dgst digest.Digest) (*manifest.SignedManifest, error) { sm, err := pms.localManifests.Get(dgst) if err == nil { proxyMetrics.ManifestPush(uint64(len(sm.Raw))) return sm, err } sm, err = pms.remoteManifests.Get(dgst) if err != nil { return nil, err } proxyMetrics.ManifestPull(uint64(len(sm.Raw))) err = pms.localManifests.Put(sm) if err != nil { return nil, err } // Schedule the repo for removal pms.scheduler.AddManifest(pms.repositoryName, repositoryTTL) // Ensure the manifest blob is cleaned up pms.scheduler.AddBlob(dgst.String(), repositoryTTL) proxyMetrics.ManifestPush(uint64(len(sm.Raw))) return sm, err }
// BuildBlobURL constructs the url for the blob identified by name and dgst. func (ub *URLBuilder) BuildBlobURL(name string, dgst digest.Digest) (string, error) { route := ub.cloneRoute(RouteNameBlob) layerURL, err := route.URL("name", name, "digest", dgst.String()) if err != nil { return "", err } return layerURL.String(), nil }
func (pbs proxyBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { desc, err := pbs.localStore.Stat(ctx, dgst) if err != nil && err != distribution.ErrBlobUnknown { return err } if err == nil { proxyMetrics.BlobPush(uint64(desc.Size)) return pbs.localStore.ServeBlob(ctx, w, r, dgst) } desc, err = pbs.remoteStore.Stat(ctx, dgst) if err != nil { return err } remoteReader, err := pbs.remoteStore.Open(ctx, dgst) if err != nil { return err } bw, isNew, cleanup, err := getOrCreateBlobWriter(ctx, pbs.localStore, desc) if err != nil { return err } defer cleanup() if isNew { go func() { err := streamToStorage(ctx, remoteReader, desc, bw) if err != nil { context.GetLogger(ctx).Error(err) } proxyMetrics.BlobPull(uint64(desc.Size)) }() err := streamToClient(ctx, w, desc, bw) if err != nil { return err } proxyMetrics.BlobPush(uint64(desc.Size)) pbs.scheduler.AddBlob(dgst.String(), blobTTL) return nil } err = streamToClient(ctx, w, desc, bw) if err != nil { return err } proxyMetrics.BlobPush(uint64(desc.Size)) return nil }
func (ms *manifests) Delete(dgst digest.Digest) error { u, err := ms.ub.BuildManifestURL(ms.name, dgst.String()) if err != nil { return err } req, err := http.NewRequest("DELETE", u, nil) if err != nil { return err } resp, err := ms.client.Do(req) if err != nil { return err } defer resp.Body.Close() if SuccessStatus(resp.StatusCode) { return nil } return handleErrorResponse(resp) }
func (pms proxyManifestStore) GetByTag(tag string, options ...distribution.ManifestServiceOption) (*manifest.SignedManifest, error) { var localDigest digest.Digest localManifest, err := pms.localManifests.GetByTag(tag, options...) switch err.(type) { case distribution.ErrManifestUnknown, distribution.ErrManifestUnknownRevision: goto fromremote case nil: break default: return nil, err } localDigest, err = manifestDigest(localManifest) if err != nil { return nil, err } fromremote: var sm *manifest.SignedManifest sm, err = pms.remoteManifests.GetByTag(tag, client.AddEtagToTag(tag, localDigest.String())) if err != nil { return nil, err } if sm == nil { context.GetLogger(pms.ctx).Debugf("Local manifest for %q is latest, dgst=%s", tag, localDigest.String()) return localManifest, nil } context.GetLogger(pms.ctx).Debugf("Updated manifest for %q, dgst=%s", tag, localDigest.String()) err = pms.localManifests.Put(sm) if err != nil { return nil, err } dgst, err := manifestDigest(sm) if err != nil { return nil, err } pms.scheduler.AddBlob(dgst.String(), repositoryTTL) pms.scheduler.AddManifest(pms.repositoryName, repositoryTTL) proxyMetrics.ManifestPull(uint64(len(sm.Raw))) proxyMetrics.ManifestPush(uint64(len(sm.Raw))) return sm, err }
func setResponseHeaders(w http.ResponseWriter, length int64, mediaType string, digest digest.Digest) { w.Header().Set("Content-Length", strconv.FormatInt(length, 10)) w.Header().Set("Content-Type", mediaType) w.Header().Set("Docker-Content-Digest", digest.String()) w.Header().Set("Etag", digest.String()) }
// loadManifest loads a manifest from a byte array and verifies its content. // The signature must be verified or an error is returned. If the manifest // contains no signatures by a trusted key for the name in the manifest, the // image is not considered verified. The parsed manifest object and a boolean // for whether the manifest is verified is returned. func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte, dgst, ref string) (*registry.ManifestData, bool, error) { sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures") if err != nil { return nil, false, fmt.Errorf("error parsing payload: %s", err) } keys, err := sig.Verify() if err != nil { return nil, false, fmt.Errorf("error verifying payload: %s", err) } payload, err := sig.Payload() if err != nil { return nil, false, fmt.Errorf("error retrieving payload: %s", err) } var manifestDigest digest.Digest if dgst != "" { manifestDigest, err = digest.ParseDigest(dgst) if err != nil { return nil, false, fmt.Errorf("invalid manifest digest from registry: %s", err) } dgstVerifier, err := digest.NewDigestVerifier(manifestDigest) if err != nil { return nil, false, fmt.Errorf("unable to verify manifest digest from registry: %s", err) } dgstVerifier.Write(payload) if !dgstVerifier.Verified() { computedDigest, _ := digest.FromBytes(payload) return nil, false, fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", manifestDigest, computedDigest) } } if utils.DigestReference(ref) && ref != manifestDigest.String() { return nil, false, fmt.Errorf("mismatching image manifest digest: got %q, expected %q", manifestDigest, ref) } var manifest registry.ManifestData if err := json.Unmarshal(payload, &manifest); err != nil { return nil, false, fmt.Errorf("error unmarshalling manifest: %s", err) } if manifest.SchemaVersion != 1 { return nil, false, fmt.Errorf("unsupported schema version: %d", manifest.SchemaVersion) } var verified bool for _, key := range keys { job := eng.Job("trust_key_check") b, err := key.MarshalJSON() if err != nil { return nil, false, fmt.Errorf("error marshalling public key: %s", err) } namespace := manifest.Name if namespace[0] != '/' { namespace = "/" + namespace } stdoutBuffer := bytes.NewBuffer(nil) job.Args = append(job.Args, namespace) job.Setenv("PublicKey", string(b)) // Check key has read/write permission (0x03) job.SetenvInt("Permission", 0x03) job.Stdout.Add(stdoutBuffer) if err = job.Run(); err != nil { return nil, false, fmt.Errorf("error running key check: %s", err) } result := engine.Tail(stdoutBuffer, 1) log.Debugf("Key check result: %q", result) if result == "verified" { verified = true } } return &manifest, verified, nil }
func (ms *manifests) Get(dgst digest.Digest) (*manifest.SignedManifest, error) { // Call by Tag endpoint since the API uses the same // URL endpoint for tags and digests. return ms.GetByTag(dgst.String()) }
func (ms *manifests) Exists(dgst digest.Digest) (bool, error) { // Call by Tag endpoint since the API uses the same // URL endpoint for tags and digests. return ms.ExistsByTag(dgst.String()) }
func (rsrbds *repositoryScopedRedisBlobDescriptorService) blobDescriptorHashKey(dgst digest.Digest) string { return "repository::" + rsrbds.repo + "::blobs::" + dgst.String() }
func (rbds *redisBlobDescriptorService) blobDescriptorHashKey(dgst digest.Digest) string { return "blobs::" + dgst.String() }